11

Entity Framework 5.0 e Sql Server CE 4.0 を使用した .NET4.0 アプリケーションがあります。

1 対多 (親/子) の関係を持つ 2 つのエンティティがあります。親の削除時に削除をカスケードするように構成しましたが、何らかの理由で機能していないようです。

これが私のエンティティの簡略化されたバージョンです:

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

親エンティティを削除しようとすると、次のようにスローされます。

1 つ以上の外部キー プロパティが null 非許容であるため、リレーションシップを変更できませんでした。リレーションシップに変更が加えられると、関連する外部キー プロパティが null 値に設定されます。外部キーが null 値をサポートしていない場合は、新しい関係を定義するか、外部キー プロパティに別の非 null 値を割り当てるか、関連のないオブジェクトを削除する必要があります。

私の関係構成は問題ないと思います(ドキュメントに従いました)。また、切り離されたエンティティの削除に関するガイドラインも検索しました。

その削除が機能しない理由が本当にわかりません。それよりも良い解決策があるはずなので、すべての子をロードして1つずつ削除し、親を削除することは避けたいと思います。

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たとえば、状態をDeleted2 番目のコンテキストに設定する前、または 2 番目のコンテキストに入る前に設定した場合、コードは機能します。

私の意見ではRemove...

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

Remove... の動作は、カスケード削除との必要な関係に期待されるものにはるかに似ているため、明らかに好ましい方法です (これはモデルの場合です)。手動による状態変更の動作が他のエンティティの状態に依存するため、使用がより困難になります。特別な場合のみの高度な使用法と考えます。

The difference is not widely known or documented. I've seen very few posts about it. The only one that I could find right now again, is this one by 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 に答える