2011-01-09 19 views
18

Estoy utilizando Entity Framework 4 con la plantilla POCO.EF4 Cast DynamicProxies al objeto subyacente

Tengo una lista donde MyObject son proxies dinámicos. Quiero utilizar XmlSerializer para serializar esta lista, pero no quiero que se serialicen como DynamicProxies sino como el objeto subyacente de POCO.

Sé acerca de ContextOptions.ProxyCreationEnabled, pero no quiero usar eso. Solo quiero saber cómo lanzar un objeto proxy a POCO subyacente para serializar.

+1

Si está utilizando WCF Services, este artículo debería ayudarlo: http://msdn.microsoft.com/en-us/library/ee705457.aspx – Devart

+0

La respuesta es [aquí] (http://stackoverflow.com/ preguntas/25770369/get-underlying-entity-object-from-entity-framework-proxy/25774651 # 25774651). –

Respuesta

1

Como no desea desactivar ProxyCreation, está atascado en los objetos DynamicProxy siempre que ponga una palabra clave virtual para la propiedad del objeto (el contexto EF hereda su objeto y reemplaza las propiedades virtuales con objetos DynamicProxy). Estos objetos DynamicProxy no heredan de sus entidades POCO, solo tienen las mismas propiedades y se pueden usar en lugar de su POCO. Si realmente debe convertir a un objeto POCO (y no creo que alguien pueda encontrar una forma de lanzarlo), puede intentar solucionarlo escribiendo el constructor de copia que copiará todas las propiedades del argumento pasado (no muy inteligente) desde el punto de vista del rendimiento, pero lo que tiene que hacer, tiene que hacer), o tal vez usando System.Xml.Serialization.XmlTypeAttribute en el objeto principal que contiene su proxy dinámico en lugar de poco para decirle al serializador cómo serializar la propiedad virtual (en qué tipo).

6

Se enfrentó al mismo problema hoy y usó Value Injecter para resolverlo. Es tan simple como:

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

¿El miembro está conectado al DbContext? –

+0

Sí, está adjunto – Korayem

0

me encontré con el mismo problema de EF 5. Yo estaba tratando de serializar mi entidad de objetos a XML. La respuesta de @Koreyam me dio una pista. Lo desarrollé un poco más. En algún lugar de mi código que llamaba el serializador como este método Serialize

string objXML = EntitySerializer.Serialize(entity); 

es genérico. Así cabecera del método es la siguiente:

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

Así que en mi cuerpo método que utilizo value injecter:

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

que acabamos de resolver mi problema para todos mis ENTIDADES.

2

Cavaré estos viejos huesos ofreciendo una solución que me ayudó. Con suerte, ayudará a alguien que lo lea.

Por lo tanto, en realidad hay dos soluciones. Si no desea que la carga diferida siempre se puede desactivar proxies dinámicos y que le dará sólo el 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;} 

} 

La otra solución es utilizar el ObjectContext para obtener el tipo de la entidad original el proxy hace las veces de :

+11

ObjectContext.GetObjectType devuelve el tipo de entidad, no un objeto de entidad. – kevinpo

1

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.