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.
nhibernate @ Chris no han utilizado desde hace bastante tiempo, pero no es que el "código" se trasladó a Asignaciones de archivos XML? – eglasius
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. –
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