2010-02-01 11 views
23

Tengo una situación en la que no puedo actualizar el registro original en la base de datos, sino crear un nuevo registro, copiar todos los campos desde antiguo y aplicar los cambios a uno nuevo. (algo como esto si se traduce al código)Linq a sql, copie la entidad original a la nueva y guarde

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; 

que da la siguiente excepción:

Cannot add an entity that already exists. 

¿Es posible hacer que funcione sin necesidad de copiar manualmente todos los campos?

Respuesta

27

Esto debería funcionar:

Clone Genérico() Método

Este método creará un clon completo de cualquier objeto por la serialización de ella. La idea original vino de ambos here y 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); 
    } 
} 

Su Ejemplo Código

Ahora, con la ayuda del método de extensión anterior, el código debería funcionar si retocado un poco:

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; 
+0

El único problema es que si alguna relatedEntities cuelgan de la entidad original cuando se clona el EntityKey sigue siendo poblado con las claves originales cuando se agrega como newTest. – Mark

+0

@Mark - Cuando implementé una funcionalidad similar, cargué() cualquier entidad secundaria del objeto que estaba clonando, de lo contrario, el serializador no las vería y se saltearía. Una vez que se cargaron, la clonación del objeto funcionó perfectamente y LINQ-to-SQL insertó automáticamente nuevos registros secundarios con las nuevas claves clonadas de los padres en .SubmitChanges(). –

+0

¿Qué quieres decir con .Load()? Estoy haciendo un LoadWith en todas las colecciones para hacer que sean serializadas, pero las entidades uno a uno son nulas después del clon (aunque los ids están configurados) y no estoy loco por el LoadWith. –

3

Se podría utilizar la reflexión para repetir las propiedades y los puso

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

Obviamente, esto tendrá que ser ampliado a ser menos propenso a errores, pero podría ser un buen comienzo.

Esto ciertamente tendrá un tiempo de ejecución más lento que si las propiedades se escribieron manualmente, me imagino.

2

También es posible que desee ver en el PLINQO. Tiene la capacidad de clonar, separar, adjuntar, serializar a xml, serializar a binario, muchas a muchas relaciones, etc ... todo listo de la caja para que no tenga que lidiar con estas características que deberían haber sido incluido en primer lugar.

http://www.plinqo.com/

4

utilizando la reflexión, puede copiar fácilmente cada atributo que no se DbGenerated. Este método probablemente no sea muy eficiente, pero funcionará en caso de necesidad.

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

¿Se supone que 'ColumnAttribute' se deriva de' System.ComponentModel.DataAnnotations.Schema' o 'System.Data.Linq.Mapping.DataAttribute'? –

3

Tal vez sería mejor si se crea un procedimiento almacenado y copiar los objetos allí en lugar de perder el tiempo en LINQ SQL limitaciones. Puede manejar errores y otros problemas fácilmente en la sintaxis sql.

3

¿Es posible hacerlo funcionar sin copiar manualmente todos los campos?

Sí - No manualmente copia todos los campos:

Usted podría utilizar AutoMapper.

Puesta en algún lugar (llamado una vez al inicio del programa):

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

luego llamar a:

var newObject = AutoMapper.Mapper.Map<MyObject>(oldObject); 
Cuestiones relacionadas