C# 如何使用递归关系向DbContext添加实体,同时避免重复添加?
我使用的是实体框架4.4,我有一个一对多关系模型,如下所示:C# 如何使用递归关系向DbContext添加实体,同时避免重复添加?,c#,entity-framework,entity-framework-4.3,C#,Entity Framework,Entity Framework 4.3,我使用的是实体框架4.4,我有一个一对多关系模型,如下所示: 类项{ 公共字符串keyPart1{get;set;} 公共字符串keyPart2{get;set;} 公共虚拟容器{get;set;} 公共字符串容器ID{get;set;} } //其思想是将许多项目分配给一个容器 类容器{ 公共字符串容器ID{get;set;} 私人ICollection项目; 公共虚拟ICollection As { 获取{return\u Items???(\u Items=new HashSet());}
类项{
公共字符串keyPart1{get;set;}
公共字符串keyPart2{get;set;}
公共虚拟容器{get;set;}
公共字符串容器ID{get;set;}
}
//其思想是将许多项目分配给一个容器
类容器{
公共字符串容器ID{get;set;}
私人ICollection项目;
公共虚拟ICollection As
{
获取{return\u Items???(\u Items=new HashSet());}
受保护集{u Items=value;}
}
}
现在,这里是DbContext:
public class StorageContext : DbContext
{
public DbSet<Item> Items { get; set; }
public DbSet<Bucket> Buckets { get; set; }
public override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Item>().HasKey( i => new { i.keyPart1, i.keyPart2 } ).HasRequired( i => i.Container );
}
}
公共类StorageContext:DbContext
{
公共数据库集项{get;set;}
公共数据库集存储桶{get;set;}
模型创建时的公共覆盖无效(DbModelBuilder modelBuilder)
{
基于模型创建(modelBuilder);
modelBuilder.Entity().HasKey(i=>new{i.keyPart1,i.keyPart2}).HasRequired(i=>i.Container);
}
}
现在,假设我有N个Item实例。每个项目都属于一个容器,该容器包含多个项目实例,每个项目实例都属于一个容器,因此模型会无休止地递归
然后,我想循环查看当前的项目实例列表,并将每个实例添加到db上下文中:
foreach (var i in LocalItemList)
{
IDbSetExtensions.AddOrUpdate<Item>(db.Items, i);
}
dbContext.SaveChanges();
foreach(LocalItemList中的变量i)
{
IDbSetExtensions.AddOrUpdate(db.Items,i);
}
dbContext.SaveChanges();
我不明白的问题是如何告诉上下文添加或更新容器
,这样我就不会得到主键重复异常。在某个时刻,我们会遇到一个项
,该项与另一个项具有相同的容器
,但在SaveChanges()
上会出现重复的主键异常
如果我
Add
一个容器到DbSet,那么项是否也添加到集合中?我如何才能将其改为添加或更新?我不确定您是否要将相关的容器
与项
一起插入数据库,或者您是否只想与现有的容器
建立关系。数据库中不存在容器
实体,并且在对象图中,每个键只有一个容器
实例(允许对这些实例进行多次引用)的可能情况应该根本不成问题,简单的代码如
foreach (var i in LocalItemList)
{
dbContext.Items.Add(i);
}
dbContext.SaveChanges();
…应该毫无例外地起作用。因此,您可能有以下两种情况之一可以解释主键约束冲突:
- 数据库中已经存在
容器
实体,在对象图中,每个键只有一个容器
实例(再次允许对这些实例进行多次引用)。这是一个简单的案例,您可以使用以下方法解决:
foreach (var i in LocalItemList)
{
dbContext.Containers.Attach(i.Container);
dbContext.Items.Add(i);
}
dbContext.SaveChanges();
如果同一个键有多个容器
实例,这将不起作用,并引发“…ObjectContext中已存在具有相同键的对象…”(或类似)异常。如果容器
在数据库中还不存在,它也将不起作用(您可能会遇到外键约束冲突)
- 如果在对象图中有多个具有相同键的
容器
对象实例,则在将实体附加或添加到上下文之前,必须将重复项替换为每个键一个唯一的实例。为了简化此过程,我将首先删除循环引用,然后使用Local
集合确定是否已附加具有相同密钥的容器
:
foreach (var i in LocalItemList)
{
i.Container.Items = null;
var attachedContainer = dbContext.Containers.Local
.SingleOrDefault(c => c.ContainerId == i.Container.ContainerId);
if (attachedContainer != null)
i.Container = attachedContainer;
else
dbContext.Containers.Attach(i.Container);
// or dbContext.Containers.Add(i.Container);
// depending on if you want to insert the Container or not
dbContext.Items.Add(i);
}
dbContext.SaveChanges();
只要您让EntityFramework知道任何预先存在的容器,它似乎工作正常
例如,此测试在空数据库上运行。两个项目都在同一个容器中,但EF只插入容器一次
[TestMethod]
public void Populate()
{
const string conStr = "Data Source=(local);Initial Catalog=EfExperiment1; Integrated Security=True";
var context = new MyDbContext(conStr);
var container = new Container { ContainerId = "12345" };
var item1 = new Item { keyPart1 = "i1k1", keyPart2 = "i1k2", Container = container };
var item2 = new Item { keyPart1 = "i2k1", keyPart2 = "i2k2", Container = container };
context.Items.Add(item1);
context.Items.Add(item2);
context.SaveChanges();
}
此测试在容器已存在时运行。通过尝试从db中读取容器,我们使EF知道任何现有实例
[TestMethod]
public void PopulateSomeMore()
{
const string conStr = "Data Source=(local);Initial Catalog=EfExperiment1; Integrated Security=True";
var context = new MyDbContext(conStr);
var container = context.Buckets.FirstOrDefault(c => c.ContainerId == "12345") ??
new Container { ContainerId = "12345" };
var item3 = new Item { keyPart1 = "i3k1", keyPart2 = "i3k2", Container = container };
var item4 = new Item { keyPart1 = "i4k1", keyPart2 = "i4k2", Container = container };
context.Items.Add(item3);
context.Items.Add(item4);
context.SaveChanges();
}
此测试不会加载现有容器,因此EF认为它需要插入,并因重复密钥错误而失败
[TestMethod]
public void PopulateSomeMoreAgain()
{
const string conStr = "Data Source=(local);Initial Catalog=EfExperiment1; Integrated Security=True";
var context = new MyDbContext(conStr);
var container = new Container { ContainerId = "12345" };
var item5 = new Item { keyPart1 = "i5k1", keyPart2 = "i5k2", Container = container };
var item6 = new Item { keyPart1 = "i6k1", keyPart2 = "i6k2", Container = container };
context.Items.Add(item5);
context.Items.Add(item6);
context.SaveChanges();
}