Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linq to sql, copy original entity to new one and save

Tags:

linq-to-sql

I have a situation when I cant just update original record in database, but instead make a new record, copy all fields from old and apply changes to new one. (something like this if translated to code)

var original = from _orig in context.Test where _orig.id == 5 select _orig;
Test newTest = new Test();
newTest = original;
newTest.id = 0;
context.Test.InsertOnSubmit(newTest);
context.SubmitChanges();
original.parent_id = newTest.id;
original.isActive = 0;

which gives the following exception:

Cannot add an entity that already exists.

Is it possible to make it work without manually copying every field?

like image 375
shkipper Avatar asked Feb 01 '10 16:02

shkipper


4 Answers

This should work:

Generic Clone() Method

This method will create a full clone of any object by serializing it. The original idea came from both here and here.

/// <summary>
/// Clones any object and returns the new cloned object.
/// </summary>
/// <typeparam name="T">The type of object.</typeparam>
/// <param name="source">The original object.</param>
/// <returns>The clone of the object.</returns>
public static T Clone<T>(this T source) {
    var dcs = new DataContractSerializer(typeof(T));
    using(var ms = new System.IO.MemoryStream()) {
        dcs.WriteObject(ms, source);
        ms.Seek(0, System.IO.SeekOrigin.Begin);
        return (T)dcs.ReadObject(ms);
    }
}

Your Code Example

Now with the help of the above extension method, your code should work if tweaked a little bit:

var original = from _orig in context.Test where _orig.id == 5 select _orig;
Test newTest = original.Clone();
newTest.id = 0;
context.Test.InsertOnSubmit(newTest);
context.SubmitChanges();
original.parent_id = newTest.id;
original.isActive = 0;
like image 110
Lance McNearney Avatar answered Nov 05 '22 12:11

Lance McNearney


Is it possible to make it work without manually copying every field?

Yes - don't manually copy every field:

You could use AutoMapper.

Set up somewhere (called once at program start):

AutoMapper.Mapper.CreateMap<MyObject, MyObject>()
// don't map the id to stop conflicts when adding into db
    .ForMember(a => a.Id, a => a.Ignore()); 

Then call:

var newObject = AutoMapper.Mapper.Map<MyObject>(oldObject);
like image 26
dav_i Avatar answered Nov 05 '22 10:11

dav_i


Using Reflection, you can easily copy each attribute that is not DbGenerated. This method is probably not very performant, but it will work in a pinch.

public static T Clone<T>(this T source)
{
    var clone = (T)Activator.CreateInstance(typeof(T));
    var cols = typeof(T).GetProperties()
        .Select(p => new { Prop = p, Attr = (ColumnAttribute)p.GetCustomAttributes(typeof(ColumnAttribute), true).SingleOrDefault() })
        .Where(p => p.Attr != null && !p.Attr.IsDbGenerated);
    foreach (var col in cols)
        col.Prop.SetValue(clone, col.Prop.GetValue(source, null), null);
    return clone;
}
like image 5
brad Avatar answered Nov 05 '22 11:11

brad


You could use reflection to iterate over the properties and set them

  foreach (var prop in original.GetType().GetProperties())
  {
     prop.SetValue(newTest, prop.GetValue(original,null), null);
  }

Obviously this will need to be expanded to be less error prone, but it could be a good start.

This will certainly have a slower runtime than if the properties were written out manually, I'd imagine.

like image 4
Mike Avatar answered Nov 05 '22 10:11

Mike