我有两个实体,一对多(父/子)关系.我已将其配置为在父删除时级联删除,但由于某种原因它似乎不起作用.
这是我的实体的简化版本:
public class Account { public int AccountKey { get; set; } public string Name { get; set; } public ICollection<User> Users { get; set; } } internal class AccountMap : EntityTypeConfiguration<Account> { public AccountMap() { this.HasKey(e => e.AccountKey); this.Property(e => e.AccountKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); this.Property(e => e.Name).Isrequired(); } } public class User { public int UserKey { get; set; } public string Name { get; set; } public Account Account { get; set; } public int AccountKey { get; set; } } internal class UserMap : EntityTypeConfiguration<User> { public UserMap() { this.HasKey(e => e.UserKey); this.Property(e => e.UserKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); this.Property(e => e.Name).Isrequired(); this.Hasrequired(e => e.Account) .WithMany(e => e.Users) .HasForeignKey(e => e.AccountKey); } } public class TestContext : DbContext { public TestContext() { this.Configuration.LazyLoadingEnabled = false; } public DbSet<User> Users { get; set; } public DbSet<Account> Accounts { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); modelBuilder.Conventions.Remove<StoreGeneratedIdentityKeyConvention>(); modelBuilder.LoadConfigurations(); } }
连接字符串:
<connectionStrings> <add name="TestContext" connectionString="Data Source=|DataDirectory|\TestDb.sdf;" providerName="System.Data.sqlServerCe.4.0" /> </connectionStrings>
以及我应用程序工作流程的简化版本:
static void Main(string[] args) { try { Database.SetInitializer(new DropCreateDatabaseAlways<TestContext>()); using (var context = new TestContext()) context.Database.Initialize(false); Account account = null; using (var context = new TestContext()) { var account1 = new Account() { Name = "Account1^" }; var user1 = new User() { Name = "User1",Account = account1 }; context.Accounts.Add(account1); context.Users.Add(user1); context.SaveChanges(); account = account1; } using (var context = new TestContext()) { context.Entry(account).State = EntityState.Deleted; context.SaveChanges(); } } catch (Exception e) { Console.WriteLine(e.ToString()); } Console.WriteLine("\nPress any key to exit..."); Console.ReadLine(); }
当我尝试删除父实体时,它会抛出:
The relationship could not be changed because one or more of the
foreign-key properties is non-nullable. When a change is made to a
relationship,the related foreign-key property is set to a null value.
If the foreign-key does not support null values,a new relationship
must be defined,the foreign-key property must be assigned another
non-null value,or the unrelated object must be deleted.
我相信我的关系配置还可以(followed the documentation).我也搜索了guidelines on deleting detached entities.
解决方法
不同之处在于,设置状态仅将根实体(传递给context.Entry的实体)的状态更改为已删除但不更改相关实体的状态,而如果使用级联删除配置关系,则删除会执行此操作.
如果你得到一个例外实际上取决于孩子(全部或只是一部分)被附加到上下文.这导致了一种有点难以遵循的行为:
>如果您调用删除,则无论子项是否已加载,都不会出现异常.仍然存在差异:
>如果子项附加到上下文,EF将为每个附加的子项生成DELETE语句,然后为父项生成(因为删除确实将它们全部标记为已删除)
>如果子项没有附加到上下文,EF将只向父数据库发送DELETE语句,并且因为启用了级联删除,数据库也将删除子项.
>如果将根实体的状态设置为“已删除”,则可能会出现异常:
>如果孩子被附加到上下文,他们的状态将不会被设置为已删除,并且EF会抱怨您尝试删除所需关系中的主体(根实体)而不删除受抚养人(子女)或至少没有将其外键设置为另一个未处于“已删除”状态的根实体.这是您的例外:帐户是根,user1是帐户和调用context.Entry(帐户).State = EntityState.Deleted;还会将状态为“未更改”的user1附加到上下文中(或者在SaveChanges中更改检测将执行此操作,我不确定是否与此相关). user1是account.Users集合的一部分,因为关系fixup在第一个上下文中将它添加到集合中,尽管您没有在代码中明确添加它.
>如果没有子项附加到上下文设置,则根状态为Deleted将向数据库发送DELETE语句,并且数据库中的级联删除也将删除子项.这无一例外地起作用.例如,如果在第二个上下文中将状态设置为Deleted之前或在输入第二个上下文之前设置account.Users = null,则代码将起作用.
在我看来使用删除…
using (var context = new TestContext()) { context.Accounts.Attach(account); context.Accounts.Remove(account); context.SaveChanges(); }
…显然是首选方式,因为Remove的行为更像是您对级联删除所需的关系(在您的模型中就是这种情况).手动状态改变的行为对其他实体的状态的依赖性使得它更难以使用.我认为它仅作为特殊情况的高级用法.
这种差异并不广为人知或有记载.我看过很少有关于它的帖子.我现在唯一能找到的就是this one by Zeeshan Hirani.