【问题标题】:Cloning data on Entity Framework [duplicate]在实体框架上克隆数据 [重复]
【发布时间】:2010-02-02 15:24:41
【问题描述】:

我正在创建软件,用户可以在其中创建基于旧产品的新产品。

现在我需要使用实体框架进行复制/克隆操作。一开始我是这样写的:

foreach(表 1 中的源数据 1) { ...创建新表 ...复制数据 ...创建指南 ... 添加 foreach(表 2 中的源数据 2) { ...创建新表 ...复制数据 ...创建指南 ... 添加 ... 等等 } }

问题是这不是一个很好的方法。是否有任何简单的方法克隆信息(除了需要为新行生成的 Guid)还是我应该手动复制所有内容?

其他解决方案

您也可以使用 EmitMapper 或 AutoMapper 来复制属性。

【问题讨论】:

    标签: entity-framework


    【解决方案1】:

    要在实体框架中克隆一个实体,您可以简单地从DataContext 中分离实体,然后将其重新添加到EntityCollection

    context.Detach(entity);
    entityCollection.Add(entity);
    

    更新 EF6+(来自 cmets)

    context.Entry(entity).State = EntityState.Detached;
    entity.id = 0;
    entity.property = value;
    context.Entry(entity).State = EntityState.Added;
    context.SaveChanges();
    

    【讨论】:

    • 对于那些尝试这种方法的人。请记住,在调用 context.SaveChanges() 之前,您不会获得新的 Id(键值)
    • 唯一的问题是如果你分离一个实体,你会丢失所有的引用。
    • 注意:如果你分离一个实体,它不会分离相关实体,所以如果你这样做 var entity = context.MyEntities.Include("MyRelated").First(t => t.ID = = 1) 后跟 Detach(entity),MyRelated 仍将在上下文中,这意味着当您调用 SaveChanges() 时它们会被无意删除。
    • 使用这种方法,linq 查询中的.AsNoTracking() 会有所帮助吗? (所以你不需要自己分离实体)?
    • 在 EF5+ 中我这样做了context.Entry(entity).State = EntityState.Detached;
    【解决方案2】:
    public static EntityObject Clone(this EntityObject Entity)
    {
        var Type = Entity.GetType();
        var Clone = Activator.CreateInstance(Type);
    
        foreach (var Property in Type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.SetProperty))
        {
            if (Property.PropertyType.IsGenericType && Property.PropertyType.GetGenericTypeDefinition() == typeof(EntityReference<>)) continue;
            if (Property.PropertyType.IsGenericType && Property.PropertyType.GetGenericTypeDefinition() == typeof(EntityCollection<>)) continue;
            if (Property.PropertyType.IsSubclassOf(typeof(EntityObject)))  continue;
    
            if (Property.CanWrite)
            {
                Property.SetValue(Clone, Property.GetValue(Entity, null), null);
            }
        }
    
        return (EntityObject)Clone;
    }
    

    这是我写的一个简单的方法。它应该适用于大多数人。

    【讨论】:

    • 这对模态对话框非常有效。您克隆对象,将其设置为模式对话框的上下文,然后当他们单击确定时将值设置回原始对象。如果他们取消你什么都不做。好简单。以前我正在撤消更改,但是如果对象已经有更改,这会变得很混乱,所以它会从一开始就被撤消。这也解决了主窗口上的绑定会随着用户在对话框中键入而更新的问题。
    • 这需要一些修复。第一个是我认为您的意思是在 foreach 语句的前三行中使用 continue 而不是 break。根据返回的属性的顺序,这将表现不正确。第二个是您需要删除 BindingFlags.DeclaredOnly ,因为这会导致它与从其他实体对象继承的实体对象一起失败。例如,在我的情况下,Staff 是从 Person 继承的,但我只是克隆了 Staff 上的属性。
    • 这也丢失了引用?
    【解决方案3】:

    要添加内容基于现有行的新行,请按以下步骤操作:

    1. 根据起始行获取实体。
    2. 将实体的进入状态设置为已添加。
    3. 修改实体。
    4. 保存更改。

    这是一个例子:

    var rabbit = db.Rabbits.First(r => r.Name == "Hopper");
    db.Entry(rabbit).State = EntityState.Added;
    rabbit.IsFlop = false;
    db.SaveChanges();
    

    【讨论】:

    • 当我尝试这个时,我收到一条错误消息,上面写着“对数据库的更改已成功提交,但在更新对象上下文时发生错误。”
    【解决方案4】:

    如果您想创建实体的副本以便稍后在代码执行中进行比较,您可以在新的数据库上下文中选择实体。

    例如,如果您正在更新一个实体,那么稍后在代码中您想要比较更新后的实体和原始实体:

    var db = new dbEntityContext();
    var dbOrig = new dbEntityContext();
    
    var myEntity = db.tblData.FirstOrDefault(t => t.Id == 123);
    var myEntityOrig = dbOrig.tblData.FirstOrDefault(t => t.Id == 123);
    
    //Update the entity with changes
    myEntity.FirstName = "Gary";
    
    //Save Changes
    db.SaveChnages();
    

    此时,myEntity.FirstName 将包含 "Gary"myEntityOrig.FirstName 将包含原始值。如果您具有记录更改的功能,您可以在其中传递更新的和原始实体,这很有用。

    【讨论】:

      【解决方案5】:

      使用泛型复制实体的一种非常短的方法(VB,抱歉)。
      它复制外键值(外部 ID)但不加载它们的相关对象。

      <Extension> _
      Public Function DuplicateEntity(Of T As {New, Class})(ctx As myContext, ent As T) As T
          Dim other As New T 'T is a proxy type, but New T creates a non proxy instance
          ctx.Entry(other).State = EntityState.Added 'attaches it to ctx
          ctx.Entry(other).CurrentValues.SetValues(ent) 'copies primitive properties
          Return other
      End Function
      

      例如:

      newDad = ctx.DuplicateEntity(oDad)
      newDad.RIDGrandpa ' int value copied
      newDad.Grandpa    ' object for RIDGrandpa above, equals Nothing(null)
      newDad.Children   ' nothing, empty
      

      在这种情况下,我不知道如何重新加载 Grandpa
      这不起作用:

      ctx.SaveChanges()
      ctx.Entry(newDad).Reload()
      

      但实际上,没问题。如果需要,我宁愿手动分配Grandpa

      newDad.Grandpa = oDad.Grandpa
      

      编辑: 作为MattW proposes in his comment,分离并找到新实体,您会加载它的子实体(而不是集合)。

      ctx.Entry(newDad).State = EntityState.Detached
      ctx.Find(newDad.RowId) 'you have to know the key name
      

      【讨论】:

      • 完美...我只是在编写一个 Web API Put 方法,并想检查一些关键字段是否没有从现有值更改 - 我想从数据库中加载实体,比较从 Web API 到值的不可变字段,复制其余部分并保存数据库副本。 SetValues 正是我正在寻找的“复制其余部分”步骤。
      • 对于重载问题,您是否尝试了Detach 后跟Find?我希望它会提供一个新的对象引用,不知道这对你来说是否有问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-13
      • 2011-07-12
      • 1970-01-01
      相关资源
      最近更新 更多