Linq to sql, скопируйте исходный объект в новый и сохраните

У меня есть ситуация, когда я не могу просто обновить исходную запись в базе данных, но вместо этого создаю новую запись, скопируйте все поля из старых и примените изменения к новой. (что-то вроде этого, если оно переведено в код)

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;

который дает следующее исключение:

Cannot add an entity that already exists.

Можно ли заставить его работать без ручного копирования каждого поля?

Ответ 1

Это должно работать:

Общий метод клонирования()

Этот метод создаст полный клон любого объекта, сериализуя его. Первоначальная идея исходила из здесь и здесь.

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

Пример кода

Теперь с помощью вышеупомянутого метода расширения ваш код должен работать, если немного изменить:

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;

Ответ 2

Используя Reflection, вы можете легко скопировать каждый атрибут, который не является DbGenerated. Этот метод, вероятно, не очень эффективен, но он будет работать в крайнем случае.

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

Ответ 3

Можно ли заставить его работать без ручного копирования каждого поля?

Да - вручную не копируйте каждое поле:

Вы можете использовать AutoMapper.

Настроить где-нибудь (один раз при запуске программы):

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

Затем вызовите:

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

Ответ 4

Вы можете использовать отражение, чтобы перебирать свойства и устанавливать их

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

Очевидно, это нужно будет расширить, чтобы быть менее склонным к ошибкам, но это может быть хорошим началом.

У этого, конечно, будет более медленное время выполнения, чем если бы свойства были выписаны вручную, я бы предположил.

Ответ 5

Возможно, было бы лучше создать хранимый proc и скопировать туда объекты вместо того, чтобы тратить время на ограничения linq-sql. Вы можете легко обрабатывать ошибки и другие проблемы в синтаксисе sql.

Ответ 6

Вы также можете посмотреть PLINQO. Он имеет возможность клонировать, отсоединять, присоединять, сериализовать в xml, сериализовать в двоичные, от многих до многих отношений и т.д.... все прямо из коробки, поэтому вам не нужно иметь дело с этими функциями, которые должны были быть в первую очередь.

http://www.plinqo.com/