C# 一对一标识关系的实体框架代码首个Fluent API配置
我的班级结构如下: 如何配置Fluent API将标识关系放入Cards表中 我是说C# 一对一标识关系的实体框架代码首个Fluent API配置,c#,entity-framework,entity-framework-6,code-first,ef-fluent-api,C#,Entity Framework,Entity Framework 6,Code First,Ef Fluent Api,我的班级结构如下: 如何配置Fluent API将标识关系放入Cards表中 我是说 卡片表主键:Id,CustomerId 卡片表FK:客户ID 我希望在将新卡分配给Customer.Card属性时删除上一张卡 所以我用这种方式定义了我的类: public class Customer { public int Id { get; private set; } public virtual Card Card { get; set; } } public abstrac
- 卡片表主键:Id,CustomerId
- 卡片表FK:客户ID
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>();
}
}
它根本不起作用。我在第二次保存时有此项,我不知道如何在此处指定标识关系:
未处理的异常:
System.Data.Entity.Infrastructure.DbUpdateException:错误或
保存不公开外键的实体时发生
属性的关系。EntityEntries属性将
返回null,因为无法将单个实体标识为
异常的来源。可以在保存时处理例外离子
通过在实体类型中公开外键属性变得更容易。
有关详细信息,请参阅InnerException。-->System.Data.Entity.Core.U
pdateException:来自“客户卡”关联的关系
处于“已删除”状态。给定多重性约束,一个
对应的“客户卡”
_目标“”也必须处于“已删除”状态
更新一对多关系很容易实现。您可以在下面找到完整的示例:
[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 });
}
}
[TestClass]
公共类UnitTest1
{
[测试方法]
公共void TestMethod1()
{
var context=新上下文();
var customer=新客户();
context.Customers.Add(客户);
customer.Cards.Add(新的Visa());
SaveChanges();
customer.Cards[0]=新美国运通();
SaveChanges();
aresequal(1,context.Cards.Count());
}
}
公共类客户
{
公众客户()
{
卡片=新列表();
}
public int Id{get;private set;}
公共虚拟列表卡{get;set;}
}
公共抽象类卡片
{
public int Id{get;private set;}
public int CustomerId{get;private set;}
}
公共类签证:信用卡
{
}
美国运通公共类:信用卡
{
}
公共类上下文:DbContext
{
静态上下文()
{
SetInitializer(新的DropCreateDatabaseAlways());
}
公共数据库集客户{get;set;}
公共数据库集卡{get;set;}
模型创建时受保护的覆盖无效(DbModelBuilder modelBuilder)
{
基于模型创建(modelBuilder);
modelBuilder.Entity()
.HasMany(c=>c.Cards)
.WithRequired()
.HasForeignKey(c=>c.CustomerId)
.WillCascadeOnDelete();
modelBuilder.Entity()
.HasKey(c=>new{c.Id,c.CustomerId});
}
}
实体框架实际上不允许这种操作。您不能仅仅通过尝试用另一个对象替换对象来从数据库中“删除”对象。即使使用级联删除,您仍然必须在实体框架中发出Delete命令,否则您的上下文中会出现孤立项。您可以尝试覆盖SaveChanges()
方法来捕获此行为,但这不是一个容易的补丁
你最好的办法是检查一张卡是否存在,如果存在,在添加新卡之前移除它。这可以很容易地包装成一个可重复的函数调用,如下所示:
public void AddCard(Customer customer, Card card, Context context)
{
if (customer.Card != null)
{
context.Cards.Remove(customer.Card);
}
customer.Card = card;
}
编辑
更清楚地说,Entity Framework不能在同一调用中批量删除关系对象和添加替换对象
这很好:
Customer.Card = null;
SaveChanges();
Customer.Card = new Amex();
SaveChanges();
注意对
SaveChanges()
的多次调用。前面提供的函数更像是一个包装函数,以避免额外的SaveChanges()
调用。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);
}
(为方便起见,添加了名称
和编号)
通常情况下,这是可以的。EF足够聪明,可以看到1:1的从属实体被替换,它只更新编号
字段(有效地删除旧卡)
但是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
中。问题可能在于你的单元测试:context.Customers.Add(new Customer())代码>不添加与var customer=new customer()相同的对象代码>谢谢,我更新了问题。我仍然不知道如何在这里指定标识关系…我猜您的卡类中缺少CustomerId,请您展示一下您是如何看到的?我已经试过了…public抽象类卡片{public int Id{get;private set;}}
看起来缺少CustomerId
删除关系会删除依赖对象。在EntityCollection上调用Remove方法会标记要删除的关系和从属对象。是的,但您没有在代码示例中调用Remove()
方法。如果将对象指定给null
,它将在下一次SaveChanges()
事件中被删除。但是,将对象指定给另一个对象是错误的,因为在SaveChanges()
同步对象之前,该对象仍处于已删除状态。您的错误甚至验证了这一点,说>关系是
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();
}
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();