13

I have a EF4.1 class X and I want to make copy of that plus all its child records. X.Y and X.Y.Z

Now if I do the following it returns error.

The property 'X.ID' is part of the object's key information and cannot be modified.

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

Or Is there other way of making copy of entity objects.

NOTE: there are multiple properties in each W,X,Y,Z.

1
  • 1
    One aside - you don't need .Include("Y") and .Include("Y.Z"). The second Include will include both Y and Z - it has to, if you think about it. Commented Nov 20, 2013 at 23:02

5 Answers 5

29

In , this is insanely easy with the DbExtensions.AsNotracking().

Returns a new query where the entities returned will not be cached in the DbContext or ObjectContext.

This appears to be the case for all objects in the object graph.

You just have to really understand your graph and what you do and don't want inserted/duplicated into the DB.

Lets assume we have objects like:

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

So in order to Duplicate a person, I need to duplicate the addresses, but I don't want to duplicate the States.

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

If you then wanted to then reuse those same objects again to insert a third duplicate, you'd either run the query again (with AsNoTracking()) or detach the objects (example):

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();
Sign up to request clarification or add additional context in comments.

10 Comments

looks like the AsNoTracking() does this trick. Otherwise It would not let me update the ID value for any Entity object.
this works great for 1 to many relationships. How would you support many to many relationships where you do an Include(x=>x.EntitiesWithManyToMany) in the AsNoTracking query? Currently it makes a deep copy of these also - i just want the lookup table link to be duplicated with new person id.
Although this deep copies the ManyToMany items, when you add the object, (assumption, I could be wrong) since the ManyToMany items exist already, they won't be added (as they have the same id that already exists).
Great thing, thanks for sharing. It is worth to mention that AsNoTracking() makes loading objects from DB much faster. In cases when no changes are needed for DB objects (GET) it is very useful.
|
9

You need to make correct deep copy of the whole entity graph - the best way is to serialize the original entity graph to memory stream and deserialize it to a new instance. Your entity must be serializable. It is often used with DataContractSerializer but you can use binary serialization as well.

5 Comments

Usually when I do that Its just recursion issues for serialization.
Object graph for type 'W' contains cycles and cannot be serialized if reference tracking is disabled. W has self joins of parent and child.
That is what I meant by making your entities serializable - you must prepare them to support serialization (preparation depends on the type of serializer you will use).
Will .net-framework or sql server or whatever be able to figure out what is going on and optimize this change-of-key query into just that, as opposed to adding a new row and deleting the old row?
@dontomaso: No. If you need to replace key the only way EF is able to handle it is insert new record and delete the old record because for EF, the key of existing entity is permanent.
3

C is not a copy it is the record, the error you are getting is because you are trying to update it's primary key, even if you weren't it still wouldn't work. You need to make a new X entity and then copy the values from the properties of the retrieved entity and then insert the new entity.

Comments

1

Not sure if it works in 4.1, from 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;
}

You can copy related entites to your cloned object now too; say you had an entity: Customer, which had the Navigation Property: Orders. You could then copy the Customer and their Orders using the above method by:

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

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

Comments

1

I use Newtonsoft.Json, and this awesome function.

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.