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/