我正在使用Entity Framework 5(DBContext
),并且试图找到最佳方法来深度复制实体(即复制实体和所有相关对象),然后将新实体保存在数据库中。我怎样才能做到这一点?我已经研究过使用扩展方法,例如,CloneHelper
但不确定是否适用于DBContext
。
Answers:
克隆实体的一种廉价的简便方法是执行以下操作:
var originalEntity = Context.MySet.AsNoTracking()
.FirstOrDefault(e => e.Id == 1);
Context.MySet.Add(originalEntity);
Context.SaveChanges();
这里的诀窍是AsNoTracking() -当您加载这样的实体时,您的上下文不知道它,并且当您调用SaveChanges时,它将像对待新实体一样对待它。
如果MySet
有引用MyProperty
并且您也想要它的副本,则只需使用Include
:
var originalEntity = Context.MySet.Include("MyProperty")
.AsNoTracking()
.FirstOrDefault(e => e.Id == 1);
DirectCast(DbContext, IObjectContextAdapter).ObjectContext.AddObject(entitySetName, entity)
dbContext,Select(x=> { a = x, ab = x.MyCollection.Where(g=>g.Id>2)}).ToList()
如果我添加AsNoTracking()
了查询中的数据丢失。
.Include("MyProperty.MyNestedObjet")
,请参阅msdn.microsoft.com/query/…–
Guid
ID赋予它们?
这是另一个选择。
在某些情况下,我更喜欢它,因为它不需要您专门运行查询来获取要克隆的数据。您可以使用此方法创建已经从数据库获得的实体的克隆。
//Get entity to be cloned
var source = Context.ExampleRows.FirstOrDefault();
//Create and add clone object to context before setting its values
var clone = new ExampleRow();
Context.ExampleRows.Add(clone);
//Copy values from source to clone
var sourceValues = Context.Entry(source).CurrentValues;
Context.Entry(clone).CurrentValues.SetValues(sourceValues);
//Change values of the copied entity
clone.ExampleProperty = "New Value";
//Insert clone with changes into database
Context.SaveChanges();
此方法将当前值从源复制到已添加的新行。
SetValues
如果新对象未附加到上下文中,则无法正常工作。引发InvalidOperationException
异常。如果您要做的只是克隆处于分离状态的实体,则可以将实体添加到上下文中,设置其当前值,然后分离它。
这是一种通用扩展方法,允许通用克隆。
您必须System.Linq.Dynamic
从nuget获取。
public TEntity Clone<TEntity>(this DbContext context, TEntity entity) where TEntity : class
{
var keyName = GetKeyName<TEntity>();
var keyValue = context.Entry(entity).Property(keyName).CurrentValue;
var keyType = typeof(TEntity).GetProperty(keyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).PropertyType;
var dbSet = context.Set<TEntity>();
var newEntity = dbSet
.Where(keyName + " = @0", keyValue)
.AsNoTracking()
.Single();
context.Entry(newEntity).Property(keyName).CurrentValue = keyType.GetDefault();
context.Add(newEntity);
return newEntity;
}
您唯一需要实现的就是GetKeyName方法。可以是从return typeof(TEntity).Name + "Id"
到的任何内容,也可以return the first guid property
返回标有的第一个属性DatabaseGenerated(DatabaseGeneratedOption.Identity)]
。
就我而言,我已经将班级标记为 [DataServiceKeyAttribute("EntityId")]
private string GetKeyName<TEntity>() where TEntity : class
{
return ((DataServiceKeyAttribute)typeof(TEntity)
.GetCustomAttributes(typeof(DataServiceKeyAttribute), true).First())
.KeyNames.Single();
}
我在Entity Framework Core中遇到了相同的问题,即当子实体延迟加载时,深度克隆涉及多个步骤。克隆整个结构的一种方法如下:
var clonedItem = Context.Parent.AsNoTracking()
.Include(u => u.Child1)
.Include(u => u.Child2)
// deep includes might go here (see ThenInclude)
.FirstOrDefault(u => u.ParentId == parentId);
// remove old id from parent
clonedItem.ParentId = 0;
// remove old ids from children
clonedItem.Parent1.ForEach(x =>
{
x.Child1Id = 0;
x.ParentId= 0;
});
clonedItem.Parent2.ForEach(x =>
{
x.Child2Id = 0;
x.ParentId= 0;
});
// customize entities before inserting it
// mark everything for insert
Context.Parent.Add(clonedItem);
// save everything in one single transaction
Context.SaveChanges();
当然,有一些方法可以使通用函数渴望加载所有内容和/或重置所有键的值,但是这应该使所有步骤变得更加清晰和可自定义(例如,对于某些子项,通过跳过它们根本不会被克隆,所有步骤包括)。