4

私は次のクラス構造を持っています:

ここに画像の説明を入力

カードテーブルに識別関係を入れるようにFluent APIを設定する方法は?

つまり

  • カード テーブル PK: Id、CustomerId
  • カード テーブル FK: CustomerId

Customer.Card プロパティに新しいカードを割り当てるときに、以前のカードを削除したいと思います。

したがって、クラスを次のように定義しました。

public class Customer
{
    public int Id { get; private set; }
    public virtual Card Card { get; set; }
}

public abstract class Card
{
    public int Id { get; private set; }
}

public class Visa : Card
{
}

public class Amex : Card
{
}

DbContext は次のようになります。

public class Context : DbContext
{
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Card> Cards { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Customer>()
            .HasRequired(c => c.Card)
            .WithRequiredPrincipal()
            .Map(a => a.MapKey("CustomerId"))
            .WillCascadeOnDelete();

        modelBuilder.Entity<Card>();
    }
}

テストは次のとおりです。

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        var context = new Context();
        var customer = new Customer();
        context.Customers.Add(customer);
        customer.Card = new Visa();
        context.SaveChanges();

        customer.Card = new Amex();
        context.SaveChanges();

        Assert.AreEqual(1, context.Customers.Count());
        Assert.AreEqual(1, context.Cards.Count());
    }
}

まったく機能しません。私はこれを2回目の保存で持っていますが、ここで識別関係を指定する方法がわかりません:

未処理の例外: System.Data.Entity.Infrastructure.DbUpdateException: リレーションシップの外部キー プロパティを公開しないエンティティの保存中にエラーまたは発生しました。単一のエンティティを例外のソースとして識別できないため、EntityEntries プロパティは null を返します。保存中の例外イオンの処理は、エンティティ タイプで外部キー プロパティを公開することで簡単に行うことができます。詳細については、InnerException を参照してください。---> System.Data.Entity.Core.U pdateException: 「Customer_Card」AssociationSet からの関係が「削除済み」状態です。多重度の制約がある場合、対応する「Customer_Card _Target」も「削除済み」状態でなければなりません。

更新1 対多の関係で機能させるのは簡単です。以下に完全な例を示します。

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        var context = new Context();
        var customer = new Customer();
        context.Customers.Add(customer);
        customer.Cards.Add(new Visa());
        context.SaveChanges();

        customer.Cards[0] = new Amex();
        context.SaveChanges();

        Assert.AreEqual(1, context.Cards.Count());
    }
}

public class Customer
{
    public Customer()
    {
        Cards = new List<Card>();
    }

    public int Id { get; private set; }
    public virtual List<Card> Cards { get; set; }
}

public abstract class Card
{
    public int Id { get; private set; }
    public int CustomerId { get; private set; }
}

public class Visa : Card
{
}

public class Amex : Card
{
}

public class Context : DbContext
{
    static Context()
    {
        Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
    }

    public DbSet<Customer> Customers { get; set; }
    public DbSet<Card> Cards { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Customer>()
            .HasMany(c => c.Cards)
            .WithRequired()
            .HasForeignKey(c => c.CustomerId)
            .WillCascadeOnDelete();

        modelBuilder.Entity<Card>()
            .HasKey(c => new { c.Id, c.CustomerId });
    }
}
4

2 に答える 2

3

EF が 1 対 1 を実装する方法は、従属エンティティに、主エンティティへの外部キーでもある主キーを持たせることです。したがって、従属の PK は、既存の原則 PK 値に自然に制限されます。

したがって、クラスを使用して、わずかに変更しました:

public class Customer
{
    public int CustomerId { get; private set; }
    public virtual Card Card { get; set; }
}

public abstract class Card
{
    public int CustomerId { get; private set; }
}

public class Visa : Card { }

public class Amex : Card { }

そしてマッピング:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Customer>().HasRequired(c => c.Card)
                                   .WithRequiredPrincipal();
    modelBuilder.Entity<Card>().HasKey(c => c.CustomerId);
}

そのため、2 つの個別のフィールドではなく、 PKFKCardのみがあります。CustomerId

しかし

これを試してみると、EF (6.1.2) にバグがあることがわかりました。これは私がしたことです:

using (var db = new TempModelsContext())
{
    var cst = new Customer { Name = "Customer1", 
                             Card = new Amex { Number = "Amex" } };
    db.Customers.Add(cst);
    db.SaveChanges();
}

using (var db = new TempModelsContext())
{
    var cst = db.Customers.Include(c => c.Card).Single(c => c.CustomerId == 1);
    cst.Card = new Visa { Number = "Visa" };
    db.SaveChanges();
}

(便宜上Nameとを追加)。Number

通常はこれで問題ありません。EF は、1 対 1 の依存エンティティが置き換えられ、フィールドを更新するNumber(古いカードを効果的に削除する)だけを確認できるほどスマートです。

しかし、EF は継承を見落としています (これにはデフォルトの TPH を使用しました)。もちろん、識別子フィールドも更新する必要がありますが、更新しません。Amexデータベースからアイテムを再取得すると、番号として「ビザ」を持つカードになります。

悲しいことに、このモデルでも、まず古いカードを削除してから、新しいカードを追加する必要があります。

var cst = db.Customers.Include(c => c.Card).Single(c => c.CustomerId == 1);
db.Cards.Remove(cst.Card);
db.SaveChanges();

cst.Card = new Visa { Number = "Visa" };
db.SaveChanges();

これは不器用ですが、これをTransactionScope.

于 2015-01-18T22:57:45.070 に答える
1

Entity Framework では、この種の操作は実際には許可されていません。オブジェクトを別のオブジェクトに置き換えようとするだけでは、オブジェクトをデータベースから「削除」することはできません。Cascade Delete を使用しても、Entity Framework で削除コマンドを発行する必要があります。そうしないと、コンテキスト内で孤立したアイテムになってしまいます。メソッドをオーバーライドしてこの動作をトラップすることはできますSaveChanges()が、簡単なパッチにはなりません。

最善の策は、カードが存在するかどうかを確認し、存在する場合は新しいカードを追加する前に削除することです。これは、次のように、繰り返し可能な関数呼び出しに簡単にまとめることができます。

public void AddCard(Customer customer, Card card, Context context)
{
    if (customer.Card != null)
    {
        context.Cards.Remove(customer.Card);
    }
    customer.Card = card;
}

編集

より明確にするために、Entity Framework は、リレーショナル オブジェクトの削除と置換オブジェクトの追加を同じSaveChanges()呼び出しにバッチ処理することはできません。

これはうまくいきます:

Customer.Card = null;
SaveChanges();
Customer.Card = new Amex();
SaveChanges();

への複数の呼び出しに注意してくださいSaveChanges()SaveChanges()前に提供された関数は、余分な呼び出しを避けるためのラッパー関数です。

于 2015-01-18T04:44:10.937 に答える