复制 Entity Framework 对象

13

我有一个EF4.1类X,我想复制它以及所有子记录。 X.Y和X.Y.Z

现在,如果我进行以下操作,它会返回错误。

'X.ID'属性是对象关键信息的一部分,无法修改。

public void CopyX(long ID)
{
    var c = db.Xs.Include("Y").Include("W").Include("Y.Z").SingleOrDefault(x => x.ID == ID);
    if (c != null)
    {
        c.ID = 0;
        c.Title = "Copy Of " + c.Title;
        for (var m = 0; m < c.Ys.Count; m++)
        {
            c.Ys[m].ID = 0;
            c.Ys[m].XID=0-m;
            for (var p = 0; p < c.Ys[m].Zs.Count; p++)
            {
                c.Ys[m].Zs[p].XID = 0 - m;
                c.Ys[m].Zs[p].ID = 0 - p;
            }
        }
        for (var i = 0; i < c.Ws.Count; i++)
        {
            c.Ws[i].ID = 0 - i;
            c.Ws[i].XID = 0;
        }
        db.Entry<Content>(c).State = System.Data.EntityState.Added;
        db.SaveChanges();
    }
}

还有其他方法可以复制实体对象吗。

注意:每个W、X、Y、Z中都有多个属性。


1
有一点需要注意 - 你不需要同时使用 .Include("Y").Include("Y.Z")。第二个 Include 将会包含 Y 和 Z - 如果你仔细想想的话,它必须这样做。 - Scott Baker
5个回答

28
中,使用 DbExtensions.AsNoTracking()方法可以实现此功能。

返回一个新的查询,其中返回的实体不会被缓存到 DbContext 或 ObjectContext 中。

这似乎适用于对象图中的所有对象。

你只需要真正了解你的对象图,并清楚自己想要插入或重复插入数据库的内容即可。

让我们假设有以下对象:

public class Person
{
  public int ID { get; set; }
  public string Name { get; set; }
  public virtual ICollection<Address> Addresses { get; set; }
}

public class Address
{
  public int ID { get; set; }
  public AddressLine { get; set; }
  public int StateID { get; set; }

  public ICollection<State> { get; set; }
}

所以为了复制一个人,我需要复制地址,但我不想复制州名。

var person = this._context.Persons
  .Include(i => i.Addresses)
  .AsNoTracking()
  .First();

// if this is a Guid, just do Guid.NewGuid();
// setting IDs to zero(0) assume the database is using an Identity Column
person.ID = 0;

foreach (var address in person.Addresses)
{
  address.ID = 0;
}

this._context.Persons.Add(person);
this._context.SaveChanges();
如果您想再次重复使用这些对象以插入第三个副本,则可以重新运行查询(使用 AsNoTracking())或分离对象(例如):
dbContext.Entry(person).State = EntityState.Detached;
person.ID = 0;
foreach (var address in person.Addresses)
{
  dbContext.Entry(address).State = EntityState.Detached;
  address.ID = 0;
}

this._context.Persons.Add(person);
this._context.SaveChanges();

1
这对于一对多的关系非常有效。如果要支持多对多的关系,您该如何在AsNoTracking查询中使用Include(x=>x.EntitiesWithManyToMany)?目前它也会深度复制这些内容 - 我只想要查找表链接与新人员ID重复。 - Leblanc Meneses
1
尽管这样会深度复制ManyToMany项目,但当您添加对象时(假设我可能错了),由于ManyToMany项目已经存在,它们不会被添加(因为它们具有已经存在的相同ID)。 - Erik Philips
1
最终我用以下方法解决了它:https://gist.github.com/leblancmeneses/6555489#file-gistfile1-cs - Leblanc Meneses
3
太好了,感谢分享。值得一提的是,AsNoTracking() 可以让从数据库加载对象的速度更快。在不需要对数据库对象进行更改(GET)的情况下非常有用。 - Konrad
创建多个副本怎么样? - michaelca
显示剩余5条评论

9
你需要正确地深度复制整个实体图 - 最好的方法是将原始实体图序列化到内存流中,然后反序列化到一个新实例。你的实体必须是可序列化的。通常使用DataContractSerializer,但也可以使用二进制序列化。

通常情况下,当我这样做时,只是序列化的递归问题。 - Anuj Pandey
如果禁用引用跟踪,则类型“W”的对象图包含循环,因此无法进行序列化。W具有父级和子级的自连接。 - Anuj Pandey
1
这就是我所说的使您的实体可序列化的意思 - 您必须准备它们以支持序列化(准备取决于您将使用的序列化器的类型)。 - Ladislav Mrnka
.NET框架或SQL Server或其他能够理解正在发生的情况并将此键变更查询优化为仅进行更改,而不是添加新行和删除旧行吗? - tomsv
@dontomaso:不行。如果你需要替换键,EF 能够处理的唯一方式是插入新记录并删除旧记录,因为对于 EF,现有实体的键是不可更改的。 - Ladislav Mrnka

3

C不是副本,它是记录。您遇到错误是因为您正在尝试更新其主键,即使您没有这样做,它仍然无法工作。您需要创建一个新的X实体,然后从检索到的实体的属性值复制并插入新实体。


1

我使用Newtonsoft.Json和这个很棒的函数。

    private static T CloneJson<T>(T source)
    {
        return ReferenceEquals(source, null) ? default(T) : JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
    }

1

不确定它是否适用于4.1,来自http://www.codeproject.com/Tips/474296/Clone-an-Entity-in-Entity-Framework-4

public static T CopyEntity<T>(MyContext ctx, T entity, bool copyKeys = false) where T : EntityObject
{
    T clone = ctx.CreateObject<T>();
    PropertyInfo[] pis = entity.GetType().GetProperties();

    foreach (PropertyInfo pi in pis)
    {
        EdmScalarPropertyAttribute[] attrs = (EdmScalarPropertyAttribute[])pi.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false);

        foreach (EdmScalarPropertyAttribute attr in attrs)
        {
            if (!copyKeys && attr.EntityKeyProperty)
                continue;

            pi.SetValue(clone, pi.GetValue(entity, null), null);
        }
    }

    return clone;
}

现在您也可以将相关实体复制到克隆对象中;比如您有一个名为“客户”的实体,其中包含导航属性“订单”。您可以通过上述方法复制客户及其订单:

Customer newCustomer = CopyEntity(myObjectContext, myCustomer, false);

foreach(Order order in myCustomer.Orders)
{
    Order newOrder = CopyEntity(myObjectContext, order, true);
    newCustomer.Orders.Add(newOrder);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接