【问题标题】:Entity Framework Code First Fluent API configuration for one to one identifying relationship用于一对一识别关系的实体框架代码优先流利 API 配置
【发布时间】:2015-03-16 09:48:22
【问题描述】:

我有以下类结构:

如何配置 Fluent API 以将识别关系放入 Cards 表中?

我是说

  • 卡片表 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());
    }
}

它根本不起作用。我在第二次保存时有这个,我不知道如何在这里指定识别关系:

未处理的异常: System.Data.Entity.Infrastructure.DbUpdateException:一个错误或 保存不公开外键的实体时发生 他们关系的属性。 EntityEntries 属性将 返回 null 因为无法将单个实体标识为 异常的来源。可以在保存的同时处理异常离子 通过在实体类型中公开外键属性变得更容易。 有关详细信息,请参阅 InnerException。 ---> System.Data.Entity.Core.U pdateException:来自“Customer_Card”关联集的关系 处于“已删除”状态。给定多重约束,一个 对应的'Customer_Card _Target' 也必须处于“已删除”状态。

更新很容易让它适用于一对多的关系。您可以在下面找到完整的示例:

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

【问题讨论】:

  • 问题可能出在你的单元测试上:context.Customers.Add(new Customer()); 没有添加与var customer = new Customer();相同的对象
  • 谢谢,我更新了问题。我仍然不知道如何在这里指定识别关系......
  • 我猜你想念 Card 类中的 CustomerId
  • 能否请您展示一下您是如何看待它的?我已经试过了……
  • public abstract class Card { public int Id { get; private set; } } 看起来不见了CustomerId

标签: c# entity-framework entity-framework-6 code-first ef-fluent-api


【解决方案1】:

EF 实现一对一的方式是使依赖实体具有一个主键,该主键也是主实体的外键。所以依赖的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);
}

所以Card 只有CustomerId 作为PK FK,而不是两个单独的字段。

但是

尝试这个,我发现 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();
}

(为方便起见,添加了NameNumber)。

通常这样就可以了。 EF 足够聪明,可以看到 1:1 依赖实体被替换,它只是更新Number 字段(有效地删除旧卡)。

但是EF忽略了继承(我使用了默认的TPH)。当然它也应该更新鉴别器字段,但它没有。如果您从数据库中重新获取项目,您最终会得到一张Amex 卡,其编号为“Visa”。

因此,遗憾的是,即使使用此模型,您也必须先移除旧卡,然后添加新卡:

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 中。

【讨论】:

  • 不,当您使用相同的 DbContext 实例时,TPT 有效。另一个实例在保存时抛出重复的主键异常...
  • 您可以考虑不使用继承。还有其他模式可以对行为差异进行编程,例如战略。与 ORM 的继承通常会导致比其(缺乏)必要性所证明的更多的问题。性能可能会受到严重影响,更何况。
【解决方案2】:

实体框架实际上并不允许这种操作。您不能简单地通过尝试用另一个对象替换它来从数据库中“删除”一个对象。即使使用 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()调用。

【讨论】:

  • msdn.microsoft.com/en-us/library/vstudio/… 删除关系会删除依赖对象。对 EntityCollection 调用 Remove 方法将关系和依赖对象都标记为删除。
  • 是的,但您没有在代码示例中调用 Remove() 方法。如果您将对象分配给null,它将在下一个SaveChanges() 事件期间被删除。但是,将对象分配给另一个对象是错误的,因为在SaveChanges() 同步对象之前,它仍处于已删除状态。您的错误甚至证实了这一点,说 >A 关系处于“已删除”状态。
  • 1) 它在空赋值后抛出,除了我。 2)我实际上在这里没有识别关系,因为我不知道在这种情况下如何配置它,所以无论如何也没什么可期待的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多