2009-02-28 11 views
5

He intentado encapsular la asignación de objetos en un repositorio de datos de proyectos. Quizás EF proporcionará el nivel de abstracción requerido, pero por una serie de razones estoy usando Linq para SQL en este momento. El siguiente código apunta a devolver los usuarios en la base de datos como una lista de objetos ModUser, donde ModUser es POCO que el repositorio expone:¿Cuál es la mejor manera de encapsular Linq a SQL acceso a datos?

public List<ModUser> GetUsers() { 
    Users.Select(MapUser).ToList(); 
} 

public Expression<Func<User, ModUser>> MapUser { 
    get { 
     return u => new ModUser() { 
      UserId = u.User_Id, 
      UserResources = u.Resources(MapResource) 
     } 
    } 
} 

public Expression<Func<Resource, ModResource>> MapResource { ... 

el código fallará, ya que no puede llamar a la expresión MapResource ya que soy tratando de llamarlo desde dentro de otra expresión. He logrado solucionar esto reemplazando 'MapResource' con u => nuevo ModResource(), luego usando el ExpressionVisitor para encontrar este nodo de marcador de posición y reemplazarlo con la expresión MapResource.

También tengo problemas similares cuando intento asignar una propiedad de ModUser con una expresión que implica una sola propiedad, es decir, UserResource = MapResource. Me las he arreglado para resolver este segundo problema al combinar manualmente las expresiones requeridas usando los métodos en la clase Expression.

me di cuenta de que podía cambiar el código anterior para

UserResources = u.Resources(r => MapResource.Compile().Invoke(r)); 

Pero entonces la consulta SQL final producido tendrá que obtener todos los atributos de r, no sólo los que necesitan los MapResouce, ya que estamos ahora lidiando con una función. Además, si MapResouce requiere acceso a otras tablas en r, no será posible ya que se está utilizando como una función, no como una expresión. Podría establecer DeferredLoadingEnabled en true, pero eso engendraría una multitud de consultas individuales en lugar de modificar la consulta principal para unirme a cualquier tabla que se requiera.

¿Alguien sabe si estas operaciones serán más fáciles en las versiones futuras de .NET o lo estoy haciendo de la manera incorrecta? Me gustan mucho las características Linq y Expression, solo desearía poder emplearlas usando un código más legible.

Actualizado Pensamiento

podría añadir algunos ejemplos de cómo he hecho las expresiones más componibles. No son concisos, pero hacen el trabajo bien.

public Expression<Func<User, ModUser>> MapUser { 
    get { 
     Expression<Func<User, ModUser>> mapUser = u => new ModUser() { 
      UserId = u.User_Id, 
      UserResources = u.Resources(r => new ModResource()) 
     }; 
     return mapUser.MapResources(this); 
    } 
} 

public Expression<Func<Resource, ModResource>> MapResource { ... } 


public static Expression<Func<T0, T1>> MapResources<T0, T1>(this Expression<Func<T0, T1>> exp, DataContext dc) { 
    return exp.Visit<MethodCallExpression, Expression<Func<T0, T1>>>(m => { 
     if(m.Arguments.Count > 1 && m.Arguments[1].Type == typeof(Func<DataContext.Resource, ModResource>)) { //Find a select statement that has the sub expression as an argument 
      //The resource mapping expression will require the Resource object, which is obtained here 
      ParameterExpression resourceParam = ((LambdaExpression)m.Arguments[1]).Parameters[0]; 
      return Expression.Call(m.Method, m.Arguments[0], //The first argument is the record selection for the 'select' method 
       Expression.Lambda<Func<DataContext.Resource, ModResource>>(//Provide the proper mapping expression as the projection for the 'select' method 
        Expression.Invoke(dc.MapResource, resourceParam), 
        resourceParam) 
       ); 
     } 
     return m; 
    }); 
} 

¿Qué estoy haciendo aquí? Tenga en cuenta que en esta versión de MapUser no creo el objeto ModResource correctamente, simplemente creo una versión ficticia. Luego llamo a un método de visitante de expresiones que busca la llamada ficticia y la reemplaza con la que originalmente quería allí. Para mí, parece que falta la sintaxis de expresión, ya que soy capaz de construir esencialmente el árbol de expresión que originalmente quería, pero tengo que visitar realmente el árbol para hacerlo. A continuación se muestra otra solución que he encontrado que se ocupa del caso singular:

public Expression<Func<User, ModUser>> MapUser { 
    get { 
     Expression<Func<User, ModResource, ModUser>> mapUser = (u, resource) => new ModUser() { 
      UserId = u.User_Id, 
      UserResource = resource; 
     } 

     return mapUser.CollapseArgument(MapResource, user => user.MainResource); 
    } 
} 

public Expression<Func<Resource, ModResource>> MapResource { ... } 

public static Expression<Func<T0, T3>> CollapseArgument<T0, T1, T2, T3>(this Expression<Func<T0, T1, T3>> exp, Expression<Func<T2, T1>> exp0, Expression<Func<T0, T2>> exp1) { 
    var param0 = Expression.Parameter(typeof(T0), "p0"); 
    var argExp = Expression.Invoke(exp0, Expression.Invoke(exp1, param0)); 
    return Expression.Lambda<Func<T0, T3>>(
     Expression.Invoke(exp, param0, argExp), 
     param0); 
} 

En este segundo ejemplo que sé que puedo obtener los datos de recursos a partir de los datos del usuario, pero no puedo "en línea" a una expresión muestre cómo hacer esto y asigne los datos del recurso a un recurso POCO. Pero puedo crear manualmente un árbol de expresiones al que se le da un recurso POCO ya mapeado y usarlo. Luego puedo crear otra expresión que muestre cómo obtener los datos sin procesar del recurso del usuario y una expresión final que muestre cómo mapear los datos de recursos sin procesar en un recurso POCO. Ahora es concebible que pueda combinar toda esta información en un único árbol de expresiones de una manera que "colapsa" el parámetro específico del recurso, ya que puedo obtenerlo del parámetro de usuario primario. Esto es lo que hace el código anterior.

Así que he encontrado formas de hacer que las expresiones sean altamente compostables ...Simplemente no se siente limpio.

Respuesta

0

Creo que si quiere usar POCO, Linq to SQL no es la mejor opción. Creo que probablemente estarías mucho mejor usando algo como NHibernate. El uso de Linq a SQL con POCOs significa que está construyendo una capa en la parte superior de la capa de datos (Linq a SQL) en la parte superior de la base de datos. Con NHibernate, estaría construyendo todo su código y asignándolo directamente a la base de datos. Menos capas == menos código == menos trabajo.

+0

nhibernate @ Chris no han utilizado desde hace bastante tiempo, pero no es que el "código" se trasladó a Asignaciones de archivos XML? – eglasius

+0

Use NHibernate con Fluidez - luego obtiene código en lugar de xml, con toda la verificación de tiempo de refactorización/compilación que espera. –

+0

Realmente estoy buscando una solución de Linq a SQL aquí, ya que mudarme a NHibernate no es una opción para este proyecto. – LaserJesus

1

La forma en que Linq to SQL admite POCO es un poco diferente.

Para lograr la ignorancia de persistencia, debe usar un archivo de asignación que describa cómo se correlaciona un modUser (columnas, asociaciones, etc.) y no el diseñador de LTS. Cuando crea un nuevo contexto, le pasa el archivo de asignación XML como XMLMappingSource.

De esta manera, LTS devolverá sus objetos de la base de datos.

He leído aquí y allá que definir las propiedades de asociación de la colección como propiedades de lectura/escritura del tipo IList (de T) es suficiente para que LinqToSQL proporcione carga diferida en esas colecciones, pero no lo he probado, así que no puedo responder por ello.

Entity Framework será aún peor para el soporte de POCO en su versión actual (básicamente ninguno en la medida en que la mayoría de las personas entienden el término POCO).

Todas las limitaciones habituales de LTS se aplican a esto, por lo que no se asigna ningún "Objeto de valor". Si desea algo más alejado de la base de datos Y soporte de POCO, entonces necesita mirar NHibernate.

+0

Un archivo de mapeo no va a tener el poder expresivo que requiero. La única forma en que puedo imaginar hacer esto es usar una serie de expresiones compostables. De hecho, tengo esto funcionando y estoy a punto de actualizar la publicación para mostrar un ejemplo. Simplemente no es tan limpio como me gustaría. – LaserJesus

1

Ok, tengo que admitir que realmente no terminé de leer la pregunta del OP (sonrisa avergonzada) pero ¿sabías que puedes usar los atributos Linq-to-SQL para decorar cualquier objeto POCO? No tienes que usar el diseñador.

Aquí hay un ejemplo al azar del código que está abierto en frente de mí en este momento. Este es un POCO llamado "Producto" que tiene algunos atributos aplicados que le permitirán interactuar con un DataContext de Linq-to-SQL.

HTH

using System; 
using System.Collections.Generic; 
using System.Data.Linq; 
using System.Data.Linq.Mapping; 
using System.Linq; 
using System.Web; 

namespace Redacted.Site.Models.Store 
{ 
    /// <summary> 
    /// A "Product" is a good for purchase at the store. 
    /// </summary> 
    [Table(Name = "s.products")] 
    public partial class Product 
    { 
     /// <summary>Gets or sets the PK of the object/row.</summary> 
     [Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL")] 
     public Int32 ID { get; set; } 

     /// <summary>Gets or sets the Title.</summary> 
     [Column(Name = "title", DbType = "NVARCHAR(500) NOT NULL")] 
     public String Title { get; set; } 

     /// <summary>Gets or sets the Lede.</summary> 
     [Column(Name = "lede", DbType = "NVARCHAR(MAX) NOT NULL")] 
     public String Lede { get; set; } 

     /// <summary>Gets or sets the Description.</summary> 
     [Column(Name = "description", DbType = "NTEXT NOT NULL")] 
     public String Description { get; set; } 

     /// <summary>Gets or sets the Price.</summary> 
     [Column(Name = "price", DbType = "FLOAT NOT NULL")] 
     public Double Price { get; set; } 

     /// <summary>Gets or sets the FK to the <see cref="Department"/>.</summary> 
     [Column(Name = "department_id", DbType = "TINYINT NOT NULL")] 
     public Byte DepartmentID { get; set; } 

     /// <summary>Gets or sets the date/time the product was released to the store.</summary> 
     [Column(Name = "released_on_utc", DbType = "DATETIME NOT NULL")] 
     public Int32 ReleasedOnUtc { get; set; } 

    } 
} 
+0

¿Es posible utilizar este enfoque para poblar los POCO que derivan sus atributos de múltiples tablas, a veces a través de relaciones no triviales? Mi respuesta inicial es que este enfoque solo funcionará para mapeos muy simples. ¿Tiene algún enlace o ejemplos de escenarios más complejos? – LaserJesus

+0

Claro. El diseñador L2S es solo un generador de código. No hay magiaEntonces puede hacer cualquier cosa que el diseñador de L2S pueda hacer en su propio código, con su propio generador. Estoy de viaje, pero corregiré esto con un ejemplo más complejo la próxima semana. – Portman

+0

Mi preocupación es que no he podido lograr lo que quiero a través del diseñador y he tenido que encontrar medios más expresivos. Pero esperaré y veré los ejemplos más complejos una vez que estén listos, gracias. – LaserJesus

Cuestiones relacionadas