[英]Entity Framework Code First Fluent API configuration for one to one identifying relationship
I have the following class structure: 我有以下类结构:
How to configure Fluent API to put identifying relationship in Cards table? 如何配置Fluent API以将标识关系放入Cards表中?
I mean 我的意思是
I would like the previous Card be deleted when I assign a new one to Customer.Card property. 我想在将新的卡分配给Customer.Card属性时删除之前的卡。
So I defined my classes this way: 所以我用这种方式定义了我的类:
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 looks like this: 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>();
}
}
Here is the test: 这是测试:
[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());
}
}
It does not work at all. 它根本不起作用。 I have this on second save and I do not know how to specify identifying relationship here: 我在第二次保存时有这个,我不知道如何在这里指定识别关系:
Unhandled Exception: System.Data.Entity.Infrastructure.DbUpdateException: An err or occurred while saving entities that do not expose foreign key properties for their relationships. 未处理的异常:System.Data.Entity.Infrastructure.DbUpdateException:保存不公开其关系的外键属性的实体时发生错误或发生。 The EntityEntries property will return null because a singl e entity cannot be identified as the source of the exception. EntityEntries属性将返回null,因为无法将单个实体标识为异常的来源。 Handling of except ions while saving can be made easier by exposing foreign key properties in your entity types. 通过在实体类型中公开外键属性,可以更轻松地在保存时处理除离子。 See the InnerException for details. 有关详细信息,请参阅InnerException。 ---> System.Data.Entity.Core.U pdateException: A relationship from the 'Customer_Card' AssociationSet is in the 'Deleted' state. ---> System.Data.Entity.Core.U pdateException:来自'Customer_Card'AssocSet的关系处于'已删除'状态。 Given multiplicity constraints, a corresponding 'Customer_Card _Target' must also in the 'Deleted' state. 给定多重约束,相应的'Customer_Card _Target'也必须处于'已删除'状态。
UPDATE It's easy to make it work for one-to-many relationships. 更新很容易让它适用于一对多的关系。 You can find a full example below: 您可以在下面找到完整示例:
[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 });
}
}
The way EF implements one-to-one is to make the dependent entity have a primary key that's also a foreign key to the principle entity. EF实现一对一的方式是使从属实体具有主键,该主键也是主要实体的外键。 So the dependent's PK is naturally constrained to existing principle PK values. 因此,依赖的PK自然受限于现有的PK值。
So using your classes, slightly modified: 所以使用你的类,稍加修改:
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 { }
And the mapping: 和映射:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().HasRequired(c => c.Card)
.WithRequiredPrincipal();
modelBuilder.Entity<Card>().HasKey(c => c.CustomerId);
}
So Card
only has CustomerId
as PK and FK, not two separate fields. 所以Card
只有CustomerId
作为PK 和 FK,而不是两个单独的字段。
BUT 但
Trying this, I found out that there's a bug in EF (6.1.2). 试着这个,我发现EF(6.1.2)中有一个错误。 This is what I did: 这就是我做的:
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();
}
(Added Name
and Number
for convenience). (为方便起见,添加了Name
和Number
)。
Normally this would be OK. 通常情况下这没关系。 EF is smart enough to see that a 1:1 dependent entity is replaced and it just updates the Number
field (effectively deleting the old card). EF很聪明,可以看到1:1依赖实体被替换,它只是更新了Number
字段(有效地删除了旧卡)。
But EF overlooks the inheritance (for which I used the default, TPH). 但EF忽略了继承 (我使用了默认的TPH)。 Of course it should also update the discriminator field, but it doesn't. 当然它也应该更新鉴别器字段,但事实并非如此。 You end up with an Amex
card, having "Visa" as number if you re-fetch the items from the database. 如果您从数据库中重新获取项目,最终会得到一张Amex
卡,并将“Visa”作为号码。
So, sadly, even with this model, you first have to remove the old card, and then add the new one: 所以,遗憾的是,即使使用此模型,您首先必须删除旧卡,然后添加新卡:
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();
This is clumsy enough, not to mention that you'd also want to wrap this in a TransactionScope
. 这很笨拙,更不用说你也想在TransactionScope
包装它。
Entity Framework doesn't really allow this sort of an operation. 实体框架实际上并不允许这种操作。 You can't "delete" an object from the database simply by trying to replace it with another object. 只需尝试将其替换为另一个对象,就无法从数据库中“删除”对象。 even with Cascade Delete, you still have to issue a delete command in Entity Framework, else you end up with an orphaned item in your context. 即使使用Cascade Delete,您仍然必须在Entity Framework中发出删除命令,否则您最终会在上下文中找到孤立项。 You can try to override the SaveChanges()
method to trap this behavior, but it won't be an easy patch. 您可以尝试覆盖SaveChanges()
方法来捕获此行为,但它不是一个简单的补丁。
your best bet would be to check if a card exists, and if so remove it before adding the new card. 您最好的选择是检查卡是否存在,如果是,请在添加新卡之前将其删除。 this can easily be wrapped up into a repeatable function call, like so: 这可以很容易地包含在一个可重复的函数调用中,如下所示:
public void AddCard(Customer customer, Card card, Context context)
{
if (customer.Card != null)
{
context.Cards.Remove(customer.Card);
}
customer.Card = card;
}
Edit 编辑
To be more clear, Entity Framework cannot batch a deletion of a relational object and the addition of a replacement object into the same SaveChanges()
call. 更清楚的是,Entity Framework无法批量删除关系对象以及将替换对象添加到同一个SaveChanges()
调用中。
This works fine: 这很好用:
Customer.Card = null;
SaveChanges();
Customer.Card = new Amex();
SaveChanges();
Note the multiple calls to SaveChanges()
. 注意对SaveChanges()
的多次调用。 The function provided earlier is more a wrapper function to avoid the extra SaveChanges()
call. 前面提供的函数更像是一个包装函数,以避免额外的SaveChanges()
调用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.