11

我有一个带有 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();
}

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

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

我相信我的关系配置没问题(遵循文档)。我还搜索了有关删除分离实体的指南

我真的不明白为什么删除不起作用。我想避免加载所有的孩子,一个一个地删除它们,然后他们删除父级,因为必须有比这更好的解决方案。

4

2 回答 2

14

将实体的状态设置为Deleted和调用DbSet<T>.Remove该实体是不一样的。

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

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

  • 如果你打电话Remove,你不会得到一个例外,无论孩子是否被加载。还是有区别的:
    • 如果孩子被附加到上下文中,EF 将为DELETE每个附加的孩子生成一个语句,然后为父母(因为Remove确实将它们都标记为Deleted
    • 如果子项未附加到上下文,EF 只会将DELETE父项的语句发送到数据库,并且由于启用了级联删除,数据库也将删除子项。
  • 如果将根实体的状态设置为,Deleted可能会出现异常:
    • 如果孩子被附加到上下文中,他们的状态将不会被设置,Deleted并且 EF 将抱怨您试图删除所需关系中的主体(根实体)而不删除依赖项(孩子)或至少不设置他们的另一个不在Deleted状态的根实体的外键。那是您遇到的例外:account是根并且user1是依赖的,account并且调用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更像您期望的与级联删除的必需关系(在您的模型中就是这种情况)。手动状态更改的行为对其他实体状态的依赖性使其更难使用。我将其视为仅用于特殊情况的高级用法。

这种差异并未广为人知或记录在案。我看到很少有关于它的帖子。我现在唯一能找到的就是Zeeshan Hirani 的这个

于 2013-05-15T23:47:17.797 回答
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)是推荐的方式。

于 2013-05-15T12:32:14.747 回答