EF4 将 DynamicProxies 转换为底层对象

发布于 2024-10-11 07:08:16 字数 237 浏览 5 评论 0原文

我正在使用 Entity Framework 4 和 POCO 模板。

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

我知道 ContextOptions.ProxyCreationEnabled,但我不想使用它。我只是想知道如何将代理对象投射到它的底层 POCO 上进行序列化。

I'm using Entity Framework 4 with POCO template.

I have a List where MyObject are dynamic proxies. I want to use the XmlSerializer to serialize this list, but I don't want them serialized as DynamicProxies but as the underlaying POCO object.

I know about ContextOptions.ProxyCreationEnabled, but I do not want to use that. I just want to know how to cast a proxy object to it's underlaying POCO to serialize.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

吹泡泡o 2024-10-18 07:08:17

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

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

Faced the same issue today and used Value Injecter to solve it. It's as simple as:

var dynamicProxyMember = _repository.FindOne<Member>(m=>m.Id = 1);
var member = new Member().InjectFrom(dynamicProxyMember) as Member;
只是一片海 2024-10-18 07:08:17

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

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

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

Ill dig these old bones up by offering a solution that helped me. Hopefully, it will help someone that reads it.

So, there are actually two solutions. If you don't want lazy loading you can always turn off dynamic proxies and that will give you just the entitiy:

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

}

The other solution is to use the ObjectContext to get the original entity type the proxy stands in for:

using (var db = new MyContext())
{
    var meanAssCat = context.MeanCats.Find(CurrentCat.Id)
    var entityType = ObjectContext.GetObjectType(meanAssCat.GetType());
}
乖乖公主 2024-10-18 07:08:17

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

As you don't want to turn ProxyCreation off, you are stuck DynamicProxy objects wherever you put virtual keyword for object property (EF Context inherits your object and replaces virtual properties with DynamicProxy objects). These DynamicProxy objects do not inherit from your POCO entities, they just have same properties and can be used instead of your POCO. If you really must to convert to POCO object (and I don't believe that someone will come up with a way to cast it), you may try to workaround by writing copy constructor which will copy all properties from passed argument (not very smart from performance standpoint, but what you have to do, you have to do), or maybe using System.Xml.Serialization.XmlTypeAttribute in parent object which contains your dynamic proxy instead of poco to tell serializer how to serialize virtual property (into which type).

月下凄凉 2024-10-18 07:08:17

免责声明:我已经为这个问题创建了一个通用的解决方案。我在寻找解决方案时发现了这个老问题,所以我想我应该在这里分享我的解决方案,以帮助那些可能在同一问题上犯错误的人。

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

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

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

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

:属性,我们需要首先实例化一个新的通用集合(我使用反射来创建一个具有正确类型参数的 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() 快得多,后者非常慢且容易出错。

希望它对某人有帮助。

Disclaimer: I've created a somewhat generic solution to this problem. I found this old question while searching for a solution so I figured I'd share my solution here to help whoever may be stubbing his or her toe on the same problem.

I ran into the same problem: I needed to get some stuff from Entity Framework, and then use ASP.NET Web Api to serialize it to XML. I've tried disabling lazy loading and proxy creation and using Include(), but on anything but the most basic class hierarchy that led to gigantic SQL queries that took several minutes to execute. I found that using lazy loading and referencing each property recursively was many, many times faster than loading the tree all at once, so I figured I'd need a way to lazy load everything, get it in the form of a POCO, and then serialize it.

I've used this answer by Gert Arnold as the basis for this solution, and then worked from there.

I've created an Unproxy method in the DBContext that takes a (proxied) class instance (something you'd get back from DbContext.Find(id) for instance) and returns that entity as an actual POCO type, with each property, sub-property etc. fully loaded and ready for serialization.

The Unproxy method and some readonly fields:

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 is a property I've added to my DBContext that simply returns all the types used in the model. That way we'll know which types we need to unproxy:

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

To deal with ICollection<> properties, we need to first instantiate a new generic collection (I'm using reflection to create a HashSet<> with the right type argument), iterate through all the values, unproxy each value and add it to the new HashSet, which is then used as the value for the POCO's property.

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

Note that I'm calling SafeUnproxy rather than Unproxy. This is because of a weird issue with type inference. Usually when you pass a proxy object to Unproxy(), type inference will infer that T is the POCO type you actually want, not the type of the dataproxy (the one that looks like YourModelPocoType_D0339E043A5559D04303M3033 etc). However, occasionally it does infer T as the dataproxy type, which blows up the

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

line, because the poco object can't be cast to the proxy type, causing the as operator to return null. To fix this, SafeUnproxy calls the Unproxy method with an explicit type parameter rather than relying on inference: it checks the type of the parameter you pass it, and if the namespace is System.Data.Entity.DynamicProxies, it'll use the type's BaseType (which in the case of a dynamicproxy type is the corresponding POCO type) as the generic type argument.

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

Making sure each property is loaded from the database is a matter of iterating through the properties of the object and checking 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();
            }
        }
    }
}

Finally, the IgnoreOnUnproxyAttribute used to avoid cycles:

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

Usage is as follows:

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

Performance isn't spectacular due to all the reflection going on, but execution time is on average only slightly (i.e. less than a second) longer than when lazy loading an entity, iterating through all its properties, and then serializing the dynamicproxy itself. Also, it's much, much faster than when using Include() which is dreadfully slow and error-prone.

Hope it helps somebody.

━╋う一瞬間旳綻放 2024-10-18 07:08:17

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

string objXML = EntitySerializer.Serialize(entity);

Serialize 方法是通用的。所以方法头是这样的:

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

所以在我的方法体中我使用 值注入器

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

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

I faced with the same issue in EF 5. I was trying to serialize my entity objects to XML. @Koreyam s answer gave me a hint. I developed it little bit more.
Somewhere in my code i was calling the serializer like this

string objXML = EntitySerializer.Serialize(entity);

Serialize method is generic. So method header is like this :

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

So in my method body i use value injecter :

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

it just solved my issue for all of my entitites.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文