删除具有一对多关系的子项的父项

时间:2013-05-15 12:32:15

标签: entity-framework .net-4.0 sql-server-ce entity-framework-5

我有一个带有Entity Framework 5.0 e Sql Server CE 4.0的.NET4.0应用程序。

我有两个实体,一对多(父/子)关系。我已将其配置为在父删除时级联删除,但由于某种原因它似乎无效。

以下是我的实体的简化版本:

    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();
}

当我尝试删除父实体时,它会抛出:

  

因为一个或多个关系无法改变   外键属性是不可为空的。当对a进行更改时   关系,相关的外键属性设置为空值。   如果外键不支持空值,则为新关系   必须定义外键属性必须另外分配   必须删除非空值或不相关的对象。

我相信我的关系配置正常(followed the documentation)。我还搜索了guidelines on deleting detached entities

我真的不明白为什么删除不起作用。我想避免加载所有子项,逐个删除它们并删除父项,因为必须有一个更好的解决方案。

2 个答案:

答案 0 :(得分:12)

将实体的状态设置为Deleted并为此实体调用DbSet<T>.Remove并不相同。

不同之处在于,设置状态只会将根实体(您传递到context.Entry的那个)的状态更改为Deleted,而不会更改相关实体的状态Remove如果使用级联删除配置关系。

如果您获得异常,实际上取决于附加到上下文的子(全部或部分)。这导致了一种有点难以遵循的行为:

  • 如果你打电话给Remove,你就不会得到例外,无论孩子是否被加载。仍然存在差异:
    • 如果子项附加到上下文,EF将为每个附加的子项生成DELETE语句,然后为父项生成{因为Remove确实将它们全部标记为Deleted)< / LI>
    • 如果孩子没有附加到上下文,EF将只向父级发送DELETE语句到数据库,并且因为启用了级联删除,数据库也会删除子级。
  • 如果您将根实体的状态设置为Deleted,则可能会获得异常:
    • 如果孩子被附加到上下文,他们的状态将不会被设置为Deleted,并且EF会抱怨您尝试删除所需关系中的主体(根实体)而不删除受抚养人(或者至少没有将其外键设置为另一个不在Deleted状态的根实体。这是您的例外情况:account是根,user1account的依赖,调用context.Entry(account).State = EntityState.Deleted;也会在状态user1附加Unchanged上下文(或SaveChanges中的更改检测将执行此操作,我不确定是否相同)。 user1account.Users集合的一部分,因为关系修正将其添加到第一个上下文中的集合中,尽管您没有在代码中明确添加它。
    • 如果没有子项附加到上下文设置,则Deleted的根状态将向数据库发送DELETE语句,并且数据库中的级联删除也将删除子项。这无一例外地起作用。例如,如果在第二个上下文中将状态设置为account.Users = null之前或在输入第二个上下文之前设置Deleted,则代码将起作用。

在我看来使用Remove ...

using (var context = new TestContext())
{
    context.Accounts.Attach(account);
    context.Accounts.Remove(account);
    context.SaveChanges();
}

...显然是首选方式,因为Remove的行为更像您对级联删除所需的关系(模型中的情况)。手动状态改变的行为对其他实体的状态的依赖性使得它更难以使用。我认为它只是特殊情况下的高级用法。

差异并不广为人知或有记载。我看过很少有关于它的帖子。我现在唯一能找到的就是this one by Zeeshan Hirani

答案 1 :(得分:1)

我尝试了一种略微不同的方法,奇怪的是,它起作用了。如果我替换此代码:

using (var context = new TestContext())
{
    context.Entry(account).State = EntityState.Deleted;
    context.SaveChanges();
}

通过这个:

using (var context = new TestContext())
{
    context.Entry(account).State = EntityState.Unchanged;
    context.Accounts.Remove(account);
    context.SaveChanges();
}

没有其他问题。不确定这是一个错误还是我错过了什么。我真的很欣赏这个问题的一些亮点,因为我很确定第一种方式(EntityState.Deleted)是推荐的方式。