Создайте неглубокую копию/клон подкласса EntityObject
-
13-12-2019 - |
Вопрос
У нас есть таблица аудита в нашей базе данных, и при обновлении старые и новые значения сериализуются в XML и сохраняются в одной строке.Таким образом, в настоящее время объекты глубоко клонированы:
public EntityObject CloneEntity(EntityObject obj)
{
DataContractSerializer dcSer = new DataContractSerializer(obj.GetType());
MemoryStream memoryStream = new MemoryStream();
dcSer.WriteObject(memoryStream, obj);
memoryStream.Position = 0;
EntityObject newObject = (EntityObject)dcSer.ReadObject(memoryStream);
return newObject;
}
Хотя это работает, оно генерирует огромные объемы данных из-за связанных записей, извлеченных из глубокого клона, с сотнями тысяч операций чтения из базы данных на dcSer.WriteObject(memoryStream, obj)
, и возможный размер потока памяти около 200 МБ, не говоря уже об объеме данных, записываемых обратно в базу данных.Не идеал.
Поэтому я хотел бы вместо этого выполнить клонирование по элементам, поскольку, насколько я понимаю, клонирование по элементам оставило бы ссылки на объекты и избежало бы копирования всех связанных моделей Entity Framework.
Итак, я сделал это:
public EntityObject CloneEntity(EntityObject obj)
{
EntityObjectAuditable auditable = (EntityObjectAuditable)obj; // invalid cast exception
return auditable.ShallowCopy();
}
// ....
public class EntityObjectAuditable : EntityObject
{
public EntityObjectAuditable ShallowCopy()
{
return (EntityObjectAuditable)this.MemberwiseClone();
}
}
но я получаю недопустимое исключение приведения, потому что фактический тип входящего EntityObject
является подклассом, относящимся к самой таблице.
Я также пробовал использовать метод расширения для доступа MemberwiseClone()
, но методы расширения не могут получить доступ к защищенным методам.
Итак, как я могу создать неглубокую копию универсального EntityObject?
Решение
Моей первой рекомендацией было бы попробовать это, что аналогично тому, что вы делаете сейчас, но сработало для меня с очень небольшими накладными расходами:
DataContractSerializationUtils.SerializeToXmlString(Entity, throwExceptions);
Кроме того, я уже успешно использовал этот метод раньше и не нахожу вывод слишком многословным.Кажется, это почти идентично тому, что вы делаете сейчас.
/// <summary>
/// Creates an exact duplicate of the entity provided
/// </summary>
/// <param name="source">The source copy of the entity</param>
/// <returns>An exact duplicate entity</returns>
public TEntity Clone(TEntity Source)
{
// Don’t serialize a null object, simply return the default for that object
if (ReferenceEquals(Source, null))
{
return default(TEntity);
}
var dcs = new DataContractSerializer(typeof (TEntity));
using (Stream stream = new MemoryStream())
{
dcs.WriteObject(stream, Source);
stream.Seek(0, SeekOrigin.Begin);
return (TEntity) dcs.ReadObject(stream);
}
}
Если ни один из этих вариантов не кажется привлекательным, я рекомендую написать короткую функцию, которая использует отражение для копирования любых свойств из исходного объекта.
Другие советы
От:
http://www.codeproject.com/Tips/474296/Clone-an-Entity-in-Entity-Framework-4.
Это гораздо эффективнее и быстрее, чем сериализация - как раз то, что вы ищете!По сути, он использует отражение для копирования необходимых свойств в новый EntityObject того же типа и способен делать это в любом классе, производном от EntityObject, используя обобщения.
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;
}
Например, предположим, что у вас была сущность:Клиент, у которого было свойство навигации:Заказы.Затем вы могли бы скопировать клиента и его заказы, используя описанный выше метод следующим образом:
Customer newCustomer = CopyEntity(myObjectContext, myCustomer, false);
foreach(Order order in myCustomer.Orders)
{
Order newOrder = CopyEntity(myObjectContext, order, true);
newCustomer.Orders.Add(newOrder);
}
Что насчет CurrentValues.ToObject() в EF 6?
var shallowCopy = (TEntity)_dbContext.Entry(entity).CurrentValues.ToObject();