用于一对一识别关系的实体框架代码优先流利 API 配置
Posted
技术标签:
【中文标题】用于一对一识别关系的实体框架代码优先流利 API 配置【英文标题】:Entity Framework Code First Fluent API configuration for one to one identifying relationship 【发布时间】: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
【参考方案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();
(为方便起见,添加了Name
和Number
)。
通常这样就可以了。 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)我实际上在这里没有识别关系,因为我不知道在这种情况下如何配置它,所以无论如何也没什么可期待的。以上是关于用于一对一识别关系的实体框架代码优先流利 API 配置的主要内容,如果未能解决你的问题,请参考以下文章
实体框架代码优先关系 - 如何定义两个对象之间的关系:两个实体之间的可选一对一