EF4 Приводит DynamicProxies к базовому объекту - PullRequest
18 голосов
/ 10 января 2011

Я использую Entity Framework 4 с шаблоном POCO.

У меня есть список, в котором MyObject - это динамические прокси. Я хочу использовать XmlSerializer для сериализации этого списка, но я не хочу, чтобы они сериализовались как DynamicProxies, а как основной объект POCO.

Я знаю о ContextOptions.ProxyCreationEnabled, но я не хочу использовать это. Я просто хочу знать, как привести прокси-объект к лежащему в его основе POCO для сериализации.

Ответы [ 5 ]

7 голосов
/ 23 января 2012

Столкнулся с той же проблемой сегодня и использовал Value Injecter для ее решения. Это так просто, как:

var dynamicProxyMember = _repository.FindOne<Member>(m=>m.Id = 1);
var member = new Member().InjectFrom(dynamicProxyMember) as Member;
2 голосов
/ 22 мая 2013

Я выкопаю эти старые кости, предложив решение, которое мне помогло.Надеюсь, это поможет кому-то, кто его читает.

Итак, на самом деле есть два решения.Если вам не нужна отложенная загрузка, вы всегда можете отключить динамические прокси, и это даст вам право:

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());
}
1 голос
/ 25 января 2016

Отказ от ответственности: Я создал несколько общее решение этой проблемы. Я нашел этот старый вопрос, когда искал решение, поэтому решил, что поделюсь своим решением здесь, чтобы помочь тому, кто, возможно, ударит ногой по этой же проблеме.

Я столкнулся с той же проблемой: мне нужно было получить кое-что из Entity Framework, а затем использовать ASP.NET Web Api для сериализации его в XML. Я пытался отключить отложенную загрузку и создание прокси-сервера и использовать Include (), но на любом другом объекте, кроме самой основной иерархии классов, которая привела к гигантским запросам SQL, выполнение которых заняло несколько минут. Я обнаружил, что использование отложенной загрузки и рекурсивной ссылки на каждое свойство было во много, много раз быстрее, чем загрузка дерева одновременно, поэтому я решил, что мне нужен способ отложенной загрузки всего, получения его в форме POCO, а затем сериализовать его.

Я использовал этот ответ Герт Арнольд в качестве основы для этого решения, а затем работал оттуда.

Я создал метод Unproxy в DBContext, который принимает (проксированный) экземпляр класса (то, что вы получите, например, из 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;

line, поскольку объект poco не может быть приведен к типу прокси, в результате чего оператор as возвращает ноль. Чтобы исправить это, SafeUnproxy вызывает метод Unproxy с явным параметром типа, а не полагается на вывод: он проверяет тип передаваемого им параметра, а если пространство имен - System.Data.Entity.DynamicProxies, он будет использовать тип BaseType (который в случае типа dynamicproxy является соответствующим типом 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 (), который ужасно медленный и подвержен ошибкам.

Надеюсь, это кому-нибудь поможет.

1 голос
/ 13 ноября 2011

Поскольку вы не хотите выключать ProxyCreation, вы зависаете в объектах DynamicProxy, где бы вы ни указывали виртуальное ключевое слово для свойства объекта (EF Context наследует ваш объект и заменяет виртуальные свойства объектами DynamicProxy).Эти объекты DynamicProxy не наследуются от ваших объектов POCO, они просто имеют те же свойства и могут использоваться вместо вашего POCO.Если вам действительно нужно преобразовать в объект POCO (и я не верю, что кто-то придумает способ его приведения), вы можете попытаться обойти это, написав конструктор копирования, который будет копировать все свойства из переданного аргумента (не очень умный).с точки зрения производительности, но то, что вы должны сделать, вы должны сделать), или, возможно, использовать System.Xml.Serialization.XmlTypeAttribute в родительском объекте, который содержит ваш динамический прокси вместо poco, чтобы указать сериализатору, как сериализовать виртуальное свойство (в какой тип).

0 голосов
/ 13 февраля 2013

Я столкнулся с той же проблемой в EF 5. Я пытался сериализовать свои объекты сущностей в XML.Ответ @Koreyam дал мне подсказку.Я разработал это немного больше.Где-то в моем коде я вызывал сериализатор как этот

string objXML = EntitySerializer.Serialize(entity);

Метод Serialize является универсальным.Таким образом, заголовок метода выглядит следующим образом:

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

Итак, в моем теле метода я использую значение injecter :

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

Это просто решило мою проблему для всех моихentitites.

...