EF4 将 DynamicProxies 转换为基础对象

Posted

技术标签:

【中文标题】EF4 将 DynamicProxies 转换为基础对象【英文标题】:EF4 Cast DynamicProxies to underlying object 【发布时间】:2011-06-06 05:19:22 【问题描述】:

我正在使用带有 POCO 模板的 Entity Framework 4。

我有一个列表,其中 MyObject 是动态代理。我想使用 XmlSerializer 序列化这个列表,但我不希望它们序列化为 DynamicProxies 而是作为底层 POCO 对象。

我知道 ContextOptions.ProxyCreationEnabled,但我不想使用它。我只是想知道如何将代理对象强制转换为它的底层 POCO 以进行序列化。

【问题讨论】:

如果你正在使用 WCF 服务,这篇文章应该对你有所帮助:msdn.microsoft.com/en-us/library/ee705457.aspx 答案是here。 【参考方案1】:

免责声明:我已经为这个问题创建了一个通用的解决方案。我在寻找解决方案时发现了这个老问题,所以我想我会在这里分享我的解决方案,以帮助任何可能在同一个问题上遇到困难的人。

我遇到了同样的问题:我需要从 Entity Framework 获取一些东西,然后使用 ASP.NET Web Api 将其序列化为 XML。我已经尝试禁用延迟加载和代理创建并使用 Include(),但是除了最基本的类层次结构之外的任何东西都会导致需要几分钟才能执行的巨大 SQL 查询。我发现使用延迟加载和递归引用每个属性比一次加载所有树要快很多很多倍,所以我想我需要一种方法来延迟加载所有内容,以 POCO 的形式获取它,然后序列化它。

我使用 Gert Arnold 的 this answer 作为此解决方案的基础,然后从那里开始工作。

我在 DBContext 中创建了一个 Unproxy 方法,该方法采用(代理)类实例(例如,您将从 DbContext.Find(id) 返回的东西)并将该实体作为实际的 POCO 类型返回,每个属性、子属性等已完全加载并准备好进行序列化。

Unproxy 方法和一些只读字段:

readonly Type ignoreOnUnproxyAttributeType = typeof(IgnoreOnUnproxyAttribute);
readonly string genericCollectionTypeName = typeof(ICollection<>).Name;

public T UnProxy<T>(T proxyObject) where T : class

    // Remember the proxyCreationEnabled value 
    var proxyCreationEnabled = Configuration.ProxyCreationEnabled;

    try
    
        Configuration.ProxyCreationEnabled = false;
        T poco = Entry(proxyObject).CurrentValues.ToObject() as T; // Convert the proxy object to a POCO object. This only populates scalar values and such, so we have to load other properties separately.

        // Iterate through all properties in the POCO type
        foreach (var property in poco.GetType().GetProperties())  
        
            // To prevent cycles, like when a child instance refers to its parent and the parent refers to its child, we'll ignore any properties decorated with a custom IgnoreOnUnproxyAttribute.
            if (Attribute.IsDefined(property, ignoreOnUnproxyAttributeType))
            
                property.SetValue(poco, null);
                continue;
            

            dynamic proxyPropertyValue = property.GetValue(proxyObject); // Get the property's value from the proxy object

            if (proxyPropertyValue != null)
            
                // If the property is a collection, get each item in the collection and set the value of the property to a new collection containing those items.
                if (property.PropertyType.IsGenericType && property.PropertyType.Name == genericCollectionTypeName)
                                            
                    SetCollectionPropertyOnPoco<T>(poco, property, proxyPropertyValue);
                
                else
                
                    // If the property is not a collection, just set the value of the POCO object to the unproxied (if necessary) value of the proxy object's property.
                    if (proxyPropertyValue != null)
                    
                        // If the type of the property is one of the types in your model, the value needs to be unproxied first. Otherwise, just set the value as is.
                        var unproxiedValue = (ModelTypeNames.Contains(property.PropertyType.Name)) ? SafeUnproxy(proxyPropertyValue) : proxyPropertyValue;
                        property.SetValue(poco, unproxiedValue);
                    
                 
            
        

        return poco; // Return the unproxied object
    
    finally
    
        // Zet ProxyCreationEnabled weer terug naar de oorspronkelijke waarde.
        Configuration.ProxyCreationEnabled = proxyCreationEnabled;
    

ModelTypeNames 是我添加到我的 DBContext 的一个属性,它只返回模型中使用的所有类型。这样我们就知道我们需要取消代理哪些类型:

private Collection<string> modelTypeNames;

private Collection<string> ModelTypeNames

    get
    
        if (modelTypeNames == null)
        
            // We'll figure out all the EF model types by simply returning all the type arguments of every DbSet<> property in the dbContext.
            modelTypeNames = new Collection<string>(typeof(VerhaalLokaalDbContext).GetProperties().Where(d => d.PropertyType.Name == typeof(DbSet<>).Name).SelectMany(d => d.PropertyType.GenericTypeArguments).Select(t => t.Name).ToList());
        

        return modelTypeNames;
    

要处理 ICollection 属性,我们需要首先实例化一个新的泛型集合(我使用反射来创建具有正确类型参数的 HashSet),遍历所有值,取消代理每个值并添加它到新的 HashSet,然后用作 POCO 属性的值。

private void SetCollectionPropertyOnPoco<T>(T poco, PropertyInfo property, dynamic proxyPropertyValue) where T : class

    // Create a HashSet<> with the correct type
    var genericTypeArguments = ((System.Type)(proxyPropertyValue.GetType())).GenericTypeArguments;
    var hashSetType = typeof(System.Collections.Generic.HashSet<>).MakeGenericType(genericTypeArguments);
    var hashSet = Activator.CreateInstance(hashSetType);

    // Iterate through each item in the collection, unproxy it, and add it to the hashset.
    foreach (var item in proxyPropertyValue)
    
        object unproxiedValue = SafeUnproxy(item);
        hashSetType.GetMethod("Add").Invoke(hashSet, new[]  unproxiedValue ); // Add the unproxied value to the new hashset
    

    property.SetValue(poco, hashSet); // Set the new hashset as the poco property value.        

请注意,我调用的是 SafeUnproxy 而不是 Unproxy。这是因为类型推断存在一个奇怪的问题。通常,当您将代理对象传递给 Unproxy() 时,类型推断会推断 T 是您真正想要的 POCO 类型,而不是 dataproxy 的类型(看起来像 YourModelPocoType_D0339E043A5559D04303M3033 等的类型)。但是,偶尔它确实将 T 推断为 dataproxy 类型,这会炸毁

T poco = Entry(proxyObject).CurrentValues.ToObject() as T;

行,因为poco对象不能强制转换为代理类型,导致as操作符返回null。为了解决这个问题,SafeUnproxy 使用显式类型参数调用 Unproxy 方法,而不是依赖于推理:它检查您传递给它的参数的类型,如果命名空间是 System.Data.Entity.DynamicProxies,它将使用类型的BaseType(在动态代理类型的情况下是对应的 POCO 类型)作为泛型类型参数。

private object SafeUnproxy(dynamic item)

    // ProxyCreation is off, so any reference or collection properties may not yet be loaded. We need to make sure we explicitly load each property from the db first.
    ExplicitlyLoadMembers(item);

    // Figure out the right type to use as the explicit generic type argument
    var itemType = item.GetType();
    Type requiredPocoType = (itemType.Namespace == "System.Data.Entity.DynamicProxies") ?
                                                                itemType.BaseType :
                                                                itemType;

    // Call Unproxy using an explicit generic type argument
    var unproxiedValue = typeof(VerhaalLokaalDbContext).GetMethod("UnProxy").MakeGenericMethod(requiredPocoType).Invoke(this, new[]  item );
    return unproxiedValue;

确保从数据库中加载每个属性只需遍历对象的属性并检查 IsLoaded:

private void ExplicitlyLoadMembers(dynamic item)

    foreach (var property in ((Type)item.GetType()).GetProperties())
    
        DbEntityEntry dbEntityEntry = Entry(item);
        var dbMemberEntry = dbEntityEntry.Member(property.Name);

        // If we're dealing with a Reference or Collection entity, explicitly load the properties if necessary.
        if (dbMemberEntry is DbReferenceEntry)
        
            if (!dbEntityEntry.Reference(property.Name).IsLoaded)
            
                dbEntityEntry.Reference(property.Name).Load();
            
        
        else if (dbMemberEntry is DbCollectionEntry)
        
            if (!dbEntityEntry.Collection(property.Name).IsLoaded)
            
                dbEntityEntry.Collection(property.Name).Load();
            
        
    

最后,IgnoreOnUnproxyAttribute 用来避免循环:

[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
sealed class IgnoreOnUnproxyAttribute : Attribute
        

用法如下:

MyDbContext db = new MyDbContext();

public Story Get(int storyId)

    var lazyStory = db.Stories.SingleOrDefault(s => s.Id == storyId);
    var unproxied = db.UnProxy(lazyStory);

    return unproxied;

由于所有反射都在进行,因此性能并不出色,但执行时间平均只比延迟加载实体、迭代其所有属性、然后序列化动态代理时稍长(即不到一秒)本身。此外,它比使用非常慢且容易出错的 Include() 时快得多。

希望它对某人有所帮助。

【讨论】:

【参考方案2】:

我会通过提供对我有帮助的解决方案来挖掘这些旧骨头。希望对阅读它的人有所帮助。

所以,实际上有两种解决方案。如果您不想延迟加载,您可以随时关闭动态代理,这将只为您提供实体:

public class MyContext : DbContext

    public MyContext()
    
        this.Configuration.ProxyCreationEnabled = false
    

    public DbSet<NiceCat> NiceCats get; set;
    public DbSet<CrazyCat> CrazyCats get; set;
    public DbSet<MeanCat> MeanCats get; set;


另一种解决方案是使用 ObjectContext 获取代理所代表的原始实体类型:

using (var db = new MyContext())

    var meanAssCat = context.MeanCats.Find(CurrentCat.Id)
    var entityType = ObjectContext.GetObjectType(meanAssCat.GetType());

【讨论】:

ObjectContext.GetObjectType 返回实体的类型,而不是实体对象。【参考方案3】:

我在 EF 5 中遇到了同样的问题。我试图将我的实体对象序列化为 XML。 @Koreyam 的回答给了我一个提示。我进一步开发了它。 在我的代码的某个地方,我像这样调用序列化程序

string objXML = EntitySerializer.Serialize(entity);

序列化方法是通用的。所以方法头是这样的:

public static string Serialize<T>(T tObj) where T : class, new()

所以在我的方法体中我使用value injecter:

T obj = new T().InjectFrom(tObj) as T;

它刚刚解决了我所有实体的问题。

【讨论】:

【参考方案4】:

今天遇到同样的问题,用Value Injecter解决了。很简单:

var dynamicProxyMember = _repository.FindOne<Member>(m=>m.Id = 1);
var member = new Member().InjectFrom(dynamicProxyMember) as Member;

【讨论】:

成员是否附加到 DbContext?【参考方案5】:

由于您不想关闭 ProxyCreation,因此无论您在何处放置对象属性的 virtual 关键字(EF Context 继承您的对象并用 DynamicProxy 对象替换虚拟属性),您都会被 DynamicProxy 对象卡住。这些 DynamicProxy 对象不会从您的 POCO 实体继承,它们只是具有相同的属性并且可以用来代替您的 POCO。如果您真的必须转换为 POCO 对象(而且我不相信有人会想出一种方法来转换它),您可以尝试通过编写复制构造函数来解决问题,该构造函数将从传递的参数中复制所有属性(不是很聪明从性能的角度来看,但你必须做的,你必须做的),或者可能在包含动态代理的父对象中使用 System.Xml.Serialization.XmlTypeAttribute 而不是 poco 来告诉序列化程序如何序列化虚拟属性(到哪种类型)。

【讨论】:

以上是关于EF4 将 DynamicProxies 转换为基础对象的主要内容,如果未能解决你的问题,请参考以下文章

将派生类转换为基类

将子类转换为基类?

泛型和强制转换 - 不能将继承的类强制转换为基类

C#通过反射将派生类转换为基类异常

将指针从派生类转换为基类的最佳方法

无法将参数 1 从派生指针转换为基类指针引用