responsabilidad: he creado una solución un tanto genérica a este problema. Encontré esta vieja pregunta mientras buscaba una solución, así que pensé que compartiría mi solución aquí para ayudar a quien sea que esté golpeándose el dedo del pie con el mismo problema.
Me encontré con el mismo problema: necesitaba obtener algunas cosas de Entity Framework, y luego utilizar ASP.NET Web Api para serializarlo en XML.He intentado deshabilitar la carga lenta y la creación de proxy y usar Include(), pero en cualquier cosa que no sea la jerarquía de clases más básica que generó consultas SQL gigantes que tardaron varios minutos en ejecutarse. Descubrí que usar cargas perezosas y hacer referencia recursivamente a cada propiedad era mucho, mucho más rápido que cargar el árbol de una sola vez, así que pensé que necesitaría una forma de cargar todo perezoso, obtenerlo en la forma de un POCO, y luego serializarlo
He usado this answer por Gert Arnold como la base para esta solución, y luego trabajé desde allí.
He creado un método Unproxy en el DBContext que toma una instancia de clase (proxied) (algo que obtendría de DbContext.Find (id) por ejemplo) y devuelve esa entidad como un tipo de POCO real, con cada propiedad, sub-propiedad etc. completamente cargada y lista para la serialización.
El método Unproxy y algunos campos de sólo lectura:
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 es una propiedad que he añadido a mi DBContext que simplemente devuelve todos los tipos utilizados en el modelo. De esta manera sabremos qué tipos necesitamos 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;
}
}
Para hacer frente a ICollection <> propiedades, necesitamos crear una instancia por primera vez una nueva colección genérica (estoy usando la reflexión para crear un HashSet <> con el argumento de tipo correcto), recorrer todos los valores, desprocesar cada valor y agregarlo al nuevo HashSet, que luego se usa como el valor para la propiedad de 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.
}
Tenga en cuenta que estoy llamando SafeUnproxy en lugar de Unproxy. Esto se debe a un problema extraño con la inferencia de tipo. Por lo general, cuando pasa un objeto proxy a Unproxy(), la inferencia de tipo inferirá que T es el tipo de POCO que realmente desea, no el tipo de dataproxy (el que se parece a YourModelPocoType_D0339E043A5559D04303M3033, etc.). Sin embargo, ocasionalmente se hace infer T como el tipo dataproxy, que explota la línea
T poco = Entry(proxyObject).CurrentValues.ToObject() as T;
, porque el objeto poco no se puede convertir al tipo de proxy, haciendo que el operador como para volver nulo. Para solucionar esto, SafeUnproxy llama al método Unproxy con un parámetro de tipo explícito en lugar de confiar en la inferencia: comprueba el tipo del parámetro que lo pasa, y si el espacio de nombre es System.Data.Entity.DynamicProxies, usará el tipo BaseType (que en el caso de un tipo de dynamicproxy es el tipo de POCO correspondiente) como el argumento de tipo genérico.
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;
}
Asegurarse de que cada propiedad se carga desde la base de datos es una cuestión de iteración a través de las propiedades del objeto y de comprobar 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();
}
}
}
}
Finalmente, el IgnoreOnUnproxyAttribute utiliza para evitar ciclos:
[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
sealed class IgnoreOnUnproxyAttribute : Attribute
{
}
uso es el siguiente:
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;
}
El rendimiento no es espectacular debido a toda la reflexión, pero el tiempo de ejecución es en promedio solo ligeramente (es decir menos de un segundo) más que cuando la carga de una entidad es vaga, itera a través de todas sus propiedades y luego serializa el propio dynamicproxy. Además, es mucho, mucho más rápido que cuando se usa Include(), que es tremendamente lento y propenso a errores.
Espero que ayude a alguien.
Si está utilizando WCF Services, este artículo debería ayudarlo: http://msdn.microsoft.com/en-us/library/ee705457.aspx – Devart
La respuesta es [aquí] (http://stackoverflow.com/ preguntas/25770369/get-underlying-entity-object-from-entity-framework-proxy/25774651 # 25774651). –