实体框架5实体的深层复制/克隆


74

我正在使用Entity Framework 5(DBContext),并且试图找到最佳方法来深度复制实体(即复制实体和所有相关对象),然后将新实体保存在数据库中。我怎样才能做到这一点?我已经研究过使用扩展方法,例如,CloneHelper但不确定是否适用于DBContext


我想深克隆/复制的实体对象使用在下面描述的反射联系,但据我所知,EntityObject派生类型不被的DbContext API支持
kypk

Answers:


129

克隆实体的一种廉价的简便方法是执行以下操作:

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);

这个把戏使我很安全了:-)。但是在我对DbContext的配置中,有一个关于无法自动添加实体的例外。我不得不经过这样的ObjectContextDirectCast(DbContext, IObjectContextAdapter).ObjectContext.AddObject(entitySetName, entity)
Patrick

我从这样的ef上下文中得到投影dbContext,Select(x=> { a = x, ab = x.MyCollection.Where(g=>g.Id>2)}).ToList()如果我添加AsNoTracking()了查询中的数据丢失。
Eldho '16

4
使用EF Core,这对我来说非常有用。但是,我必须将父级和嵌套对象上的主键设置为Guid.Empty,以防止EF尝试在数据库中插入重复的行。如果您使用整数键,我怀疑将它们设置为0会产生相同的效果。
agileMike

1
必知:您可以使用点运算符来包含嵌套元素,例如: .Include("MyProperty.MyNestedObjet"),请参阅msdn.microsoft.com/query/…–
Malick

1
复制嵌套实体时,是否需要遍历它们并将所有新GuidID赋予它们?
火车

23

这是另一个选择。

在某些情况下,我更喜欢它,因为它不需要您专门运行查询来获取要克隆的数据。您可以使用此方法创建已经从数据库获得的实体的克隆。

此方法将当前值从源复制到已添加的新行。


2
如果您希望将克隆副本和原始版本的更新一起插入一个SaveChanges中
非常有用

1
SetValues如果新对象未附加到上下文中,则无法正常工作。引发InvalidOperationException异常。如果您要做的只是克隆处于分离状态的实体,则可以将实体添加到上下文中,设置其当前值,然后分离它。
Suncat2000

4
这不是深度克隆。问题标题和文本询问有关“复制实体和所有相关对象”的深层克隆。
Zach Mierzejewski

2

这是一种通用扩展方法,允许通用克隆。

您必须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();
    }

1

我在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();

当然,有一些方法可以使通用函数渴望加载所有内容和/或重置所有键的值,但是这应该使所有步骤变得更加清晰和可自定义(例如,对于某些子项,通过跳过它们根本不会被克隆,所有步骤包括)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.