Подтвердить что ты не робот

EF4 Вставить DynamicProxies в базовый объект

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 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());
}

Ответ 3

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

Ответ 4

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

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

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

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

Чтобы иметь дело с свойствами ICollection < > , нам нужно сначала создать экземпляр нового коллектора (я использую отражение для создания HashSet < > с аргументом правильного типа), итерации по всем значениям, unproxy каждое значение и добавление это новый 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 (который в случае типа 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(), который ужасно медленный и подверженный ошибкам.

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

Ответ 5

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

string objXML = EntitySerializer.Serialize(entity);

Сериализованный метод является общим. Итак, заголовок метода выглядит следующим образом:

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

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

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

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