【问题标题】:Entity Framework 5 deep copy/clone of an entityEntity Framework 5 实体的深拷贝/克隆
【发布时间】:2013-03-09 08:25:40
【问题描述】:

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

【问题讨论】:

  • 我尝试使用以下 link 中描述的反射深度克隆/复制 Entity 对象,但据我了解,DbContext API 不支持 EntityObject 派生类型

标签: entity-framework-5 dbcontext


【解决方案1】:

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

var originalEntity = Context.MySet.AsNoTracking()
                             .FirstOrDefault(e => e.Id == 1);
Context.MySet.Add(originalEntity);
Context.SaveChanges();

这里的技巧是 AsNoTracking() - 当您加载这样的实体时,您的上下文不知道它,当您调用 SaveChanges 时,它会将其视为一个新实体。 p>

如果MySet 有对MyProperty 的引用并且您也想要它的副本,只需使用Include

var originalEntity = Context.MySet.Include("MyProperty")
                            .AsNoTracking()
                            .FirstOrDefault(e => e.Id == 1);

【讨论】:

  • 这个技巧让我很安全:-)。但是在我的 DbContext 配置中,出现了一个关于无法自动添加实体的异常。我必须像这样DirectCast(DbContext, IObjectContextAdapter).ObjectContext.AddObject(entitySetName, entity)
  • 我有像这样dbContext,Select(x=> { a = x, ab = x.MyCollection.Where(g=>g.Id>2)}).ToList()这样的ef上下文的投影如果我添加AsNoTracking()查询中有数据丢失。
  • 这对我使用 EF Core 非常有用。但是,我必须将父对象和嵌套对象上的主键设置为 Guid.Empty 以防止 EF 尝试在数据库中插入重复的行。如果您使用整数键,我怀疑将它们设置为 0 会产生相同的效果。
  • 很高兴知道:您可以使用点运算符包含嵌套元素,例如:.Include("MyProperty.MyNestedObjet"),请参阅msdn.microsoft.com/query/…
  • 当我复制嵌套实体时,我是否必须遍历它们并为它们提供所有新的GuidId?
【解决方案2】:

这是另一种选择。

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

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

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

【讨论】:

  • 如果您希望将克隆与原始更新一起插入到一个 SaveChanges 中,这非常有用
  • 如果新对象未附加到上下文,SetValues 将不起作用。它会引发InvalidOperationException 异常。如果您只想克隆处于分离状态的实体,则可以将实体添加到上下文中,设置其当前值,然后将其分离。
  • 这不是一个 deep 克隆。问题标题和文本询问有关“复制实体和所有相关对象”的深度克隆。
【解决方案3】:

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

您必须从 nuget 获取 System.Linq.Dynamic

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

【讨论】:

    【解决方案4】:

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

    当然,有一些方法可以使通用函数急切加载所有内容和/或重置所有键的值,但这应该使所有步骤更加清晰和可定制(例如,所有这些都可以让一些孩子根本不被克隆,通过跳过他们的包含)。

    【讨论】:

      猜你喜欢
      • 2010-11-30
      • 2011-09-05
      • 1970-01-01
      • 2011-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-25
      相关资源
      最近更新 更多