实体框架 6:除 ID 之外的克隆对象 [关闭]

Posted

技术标签:

【中文标题】实体框架 6:除 ID 之外的克隆对象 [关闭]【英文标题】:Entity Framework 6: Clone object except ID [closed] 【发布时间】:2014-11-01 11:15:03 【问题描述】:

在我的 MVVM 程序中,我有一个模型类(比如MyModel),我有一个从数据库读取的实例(使用实体框架)。检索对象时,我将所有数据呈现给用户。稍后用户将修改一些字段。 我想要的是创建相同的对象,除了它是 ID(因为 ID主键自动递增)。 那么我该如何处理呢?我不想一个一个地复制所有字段,这不是一个可靠的方法。因为也许将来模型可能会被修改,所以这种方式我必须在克隆方法中考虑到这一点。

那么有没有什么优雅的方法可以复制对象并在数据库中保存时,它的 ID 会再次自动递增? (将 ID 设置为 null 会给我一个编译器错误,因为它的类型是 int)。

【问题讨论】:

ValueInjecter 怎么样?不幸的是,它制作的是浅拷贝,而不是深拷贝。 valueinjecter.codeplex.com 你能告诉我们你的模型类吗?如果不是,它是 POCO 吗?它有复杂的属性吗? 刚刚读完,在思考之前,我会说,加载然后分离,您将准备好插入副本。现在我们必须谈论@GeorgeVovos 建议的复杂类型 可能,如果 OP 刚刚尝试添加设置了 ID 值的实体,他们会注意到它可以正常工作(在 EF6 及更低版本中)。不幸的是,问题似乎不是源于任何实际的代码问题。 【参考方案1】:

我注意到不需要复制。显然,在向数据库添加模型实例时(即使 ID 设置为数据库中已经存在的 ID),Entity Framework 会在数据库中插入一个新行并自动增加它的主键。所以这个功能已经内置在 EF 中。我不知道这个,对不起。 为了清楚起见,这里举个例子:

using(var database = new MyDbContext()) 
    MyModel myModel = database.FirstOrDefault(m => m.SomeProperty == someValue);
    myModel.SomeOtherProperty = someOtherValue; //user changed a value
    database.MyModels.Add(myModel); //even though the ID of myModel exists in the database, it gets added as a new row and the ID gets auto-incremented 
    database.SaveChanges();

【讨论】:

Where 会返回一个集合。如果您的实体有一个对象或对象集合,这也不起作用。至少不适合我。 @THEStephenStanton .Where() linq 扩展方法只是一个例子。为此,您还可以在实体框架DbSet<T> 上使用.Find() 方法。但这不是重点,重点是当您将脏模型添加到 DbContext 时,EF 会自动增加 ID。 codereview.stackexchange.com/questions/125149/… @kiml42 - 调用 SaveChanges() 后,脏模型对象将更新为新 ID; 不确定这是如何被接受的答案:我试了一下,它仍然给出“重复键”异常【参考方案2】:

Lori Peterson 建议使用 .AsNoTracking() 在 EF6 中执行克隆。我正在使用这种方法,并且可以确认它有效。您甚至可以包含子对象。

var entity = context.Entities
                    .AsNoTracking()
                    .Include(x => x.ChildEntities)
                    .FirstOrDefault(x => x.EntityId == entityId);

entity.SomeProperty = DateTime.Now;

context.Entities.Add(entity);
context.SaveChanges();

当您从数据集中检索一个或多个实体时,您可以告诉 Entity Framework 不要跟踪您对该对象所做的任何更改,然后将该实体作为新实体添加到数据集中。使用 .AsNoTracking,上下文对现有实体一无所知。

【讨论】:

我使用了这种方法,但必须将 Include 部分修改为 .Include("ChildEntities") - 来自 msdn:要在查询结果中返回的相关对象的点分隔列表。 我正在使用这种方法,但是,当我将实体添加到它抱怨多重性约束的上下文时,就好像子实体没有增加它的主键一样。有其他人遇到过这个问题并知道如何解决吗? @Dalton 我建议输入一个新问题,并包含相关代码。我不确定是什么导致了您提到的问题。 我添加了我的上下文的第二个临时实例,并用它来添加新实体并解决了我的问题。 我最近也遇到了@Daltons 问题,关系为 0..1。【参考方案3】:

当使用 ObjectContext 时,QuantumHive 提供的答案不起作用。

在那种情况下返回的错误是:

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
System.InvalidOperationException: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
   at System.Data.Objects.ObjectStateManager.AddEntry(IEntityWrapper wrappedObject, EntityKey passedKey, EntitySet entitySet, String argumentName, Boolean isAdded)
   at System.Data.Objects.ObjectContext.AddSingleObject(EntitySet entitySet, IEntityWrapper wrappedEntity, String argumentName)
   at System.Data.Objects.DataClasses.RelatedEnd.AddEntityToObjectStateManager(IEntityWrapper wrappedEntity, Boolean doAttach)
   at System.Data.Objects.DataClasses.RelatedEnd.AddGraphToObjectStateManager(IEntityWrapper wrappedEntity, Boolean relationshipAlreadyExists, Boolean addRelationshipAsUnchanged, Boolean doAttach)
   at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedTarget, Boolean applyConstraints, Boolean addRelationshipAsUnchanged, Boolean relationshipAlreadyExists, Boolean allowModifyingOtherEndOfRelationship, Boolean forceForeignKeyChanges)
   at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedEntity, Boolean applyConstraints)
   at System.Data.Objects.DataClasses.EntityReference`1.set_ReferenceValue(IEntityWrapper value)
   at System.Data.Objects.DataClasses.EntityReference`1.set_Value(TEntity value)

要正确克隆一个实体框架对象(至少在 EF6.0 中)是:

/// <summary>
/// Clone a replica of this item in the database
/// </summary>
/// <returns>The cloned item</returns>
public Item CloneDeep()

    using (var context = new EntityObjectContext())
    
        var item = context.Items
            .Where(i => i.ItemID == this.ItemID)
            .Single();
        context.Detach(item);
        item.EntityKey = null;
        item.ItemID = 0;
        return item;
    

【讨论】:

这取决于您的上下文,如果您在插入重复键之前没有调用context.SaveChanges(),那么底层附加实体可能具有重复键,这就是异常消息正确抱怨的原因。所以这完全取决于你的用例和实现。您应该将具有相同键的重复实体附加到相同的上下文。在我的示例中,我显然是在从新的数据库上下文中查询一个实体,所以在这种情况下它确实有效。【参考方案4】:

我发现这是想看看是否有比我目前使用的更好的方法来克隆对象,并注意到如果您尝试进行多个克隆,则接受的答案可能存在问题......至少如果您想避免多次创建上下文...

我不知道这是否是克隆的最佳方法,这就是我寻找另一种方法的原因。但是,它有效。如果你需要多次克隆一个实体,你可以使用 JSON 序列化来克隆……类似这样的东西(使用 Newtonsoft JSON)。

using( var context = new Context() ) 
    Link link    = context.Links.Where(x => x.Id == someId);
    bool isFirst = true;
    foreach( var id in userIds ) 
        if( isFirst ) 
            link.UserId = id;
            isFirst     = false;
        
        else 
            string cloneString = JsonConvert.SerializeObject(link);
            Link clone = JsonConvert.DeserializeObject<Link>(cloneString);
            clone.UserId = id;
            context.Links.Add(clone);
        
    
    context.SaveChanges();

【讨论】:

只是一个简短的说明。出于某种原因,我们项目中的 DBA 将枚举设计为小数(2,0),并且在克隆这样的实体时,例如,1 的道具将变为 1.0,并且 db 将抛出以下异常“参数值” 1.0' 超出范围” - 避免将道具转换为 int 并重新分配它 该解决方案的另一个问题是,如果您有循环引用,则 json 序列化方法将不起作用。示例User.Group.Users【参考方案5】:

我使用 postgres 数据库:

CREATE TABLE public."Table" ( 
    "Id" integer NOT NULL DEFAULT nextval('"Table_Id_seq"'::regclass),
    ...

没有任何一种方法在我的情况下不起作用。我第二个使用:

Table table = _context.Table.AsNoTracking().Select(s => new Table 
 // some properties, exept id
    ).FirstOrDefault();
_context.Table.Add(table);
await _context.SaveChangesAsync();

【讨论】:

以上是关于实体框架 6:除 ID 之外的克隆对象 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

如何在实体框架中定义除默认方法之外的外键关系

NSPredicate 返回除给定对象之外的所有对象

核心数据 - 具有一对多关系的实体正在检索除“关系”实体之外的所有实体对象的属性

如何使用实体框架检索插入实体的 ID? [关闭]

实体框架6:多对多关系问题[关闭]

如何在使用除 Id 之外的字段获取数据时实现二级缓存