创建EntityObject子类的浅拷贝/克隆

3

我们的数据库中有一个审计表,在更新时,旧值和新值被序列化为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;
}

虽然这样可以工作,但是由于从深度克隆中提取的相关记录需要读取数十万次DB,dcSer.WriteObject(memoryStream, obj) 导致生成大量数据,最终的 MemoryStream 大小约为 200MB,更不用说写回到 DB 的数据量。不是理想的方案。
因此,我想执行成员按需克隆(memberwise clone),因为我了解到它会将对象引用留出,并避免复制所有相关的 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的浅表副本呢?


我看到这个问题是一段时间之前提出的,但是我也遇到了完全相同的问题,并想出了一个解决方案,所以我已经将其作为答案添加了进来,以防其他人也想知道! - markmnl
3个回答

3

我首先建议您尝试这个方法,它与您现在所做的类似,但可行性更高且开销很小:

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

如果以上两种选项都不太吸引人,我的建议是编写一个短小的函数,使用反射来复制源实体中的任何属性。

感谢您的回复。DataContractSerializationUtils.SerializeToXmlString似乎是来自West Wind Web Toolkit的东西,我不想在我的控制台应用程序中包含它;第二种方法遇到了与我目前所做的完全相同的问题;至于第三种方法?呃,反射。创建一个浅克隆应该不应该这么困难和脆弱,特别是考虑到MemberwiseClone已经存在。 - sennett
天哪,我刚才读了一下我的评论,感觉自己像个彻头彻尾的白痴!非常抱歉。非常感谢您的帮助。最终我使用了反射,并且这个答案在实例化一个新类型时对我很有帮助:https://dev59.com/hXVD5IYBdhLWcg3wXaid - sennett
我同意这不应该那么难。很高兴能帮忙! - msigman

3

来自:

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

0

EF 6 中的 CurrentValues.ToObject() 怎么样?

var shallowCopy = (TEntity)_dbContext.Entry(entity).CurrentValues.ToObject();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接