Отказ от ответственности: Я создал несколько общее решение этой проблемы. Я нашел этот старый вопрос, когда искал решение, поэтому решил, что поделюсь своим решением здесь, чтобы помочь тому, кто, возможно, ударит ногой по этой же проблеме.
Я столкнулся с той же проблемой: мне нужно было получить кое-что из 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 (), который ужасно медленный и подвержен ошибкам.
Надеюсь, это кому-нибудь поможет.