我正在使用 Entity Framework 5 ( DBContext
) 并试图找到深度复制实体的最佳方法(即复制实体和所有相关对象),然后将新实体保存在数据库中。我怎样才能做到这一点?我已经研究过使用扩展方法,例如,CloneHelper
但我不确定它是否适用于DBContext
.
4 回答
克隆实体的一种廉价简单的方法是执行以下操作:
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);
这是另一种选择。
在某些情况下我更喜欢它,因为它不需要您专门运行查询来获取要克隆的数据。您可以使用此方法创建已从数据库中获取的实体的克隆。
//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();
此方法将当前值从源复制到已添加的新行。
这是允许通用克隆的通用扩展方法。
您必须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"
toreturn 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();
Of course, there are ways to make generic functions to eager load everything and/or reset values for all keys, but this should make all the steps much clear and customizable (e.g. all for some children to not be cloned at all, by skipping their Include).