Las*_*sus 5 .net lambda design-patterns expression-trees linq-to-sql
我一直在尝试将对象映射封装在项目数据存储库中.也许EF将提供所需的抽象级别,但由于一系列原因我目前正在使用Linq to SQL.以下代码旨在将数据库中的用户作为ModUser对象列表返回,其中ModUser是存储库公开的POCO:
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 { ...
Run Code Online (Sandbox Code Playgroud)
代码将失败,因为我无法调用MapResource表达式,因为我试图从另一个表达式中调用它.我设法通过用'=> new ModResource()替换'MapResource'来解决这个问题,然后使用ExpressionVisitor找到这个占位符节点并用MapResource表达式替换它.
当我尝试使用涉及单个属性的表达式(即UserResource = MapResource)分配ModUser的属性时,我也遇到了类似的问题.我已经设法通过使用Expression类上的方法手动组合所需的表达式来解决第二个问题.
我意识到我可以将上面的代码更改为
UserResources = u.Resources(r => MapResource.Compile().Invoke(r));
Run Code Online (Sandbox Code Playgroud)
但是生成的最终SQL查询需要获取r的所有属性,而不仅仅是MapResouce所需的属性,因为我们现在处理的是一个函数.此外,如果MapResouce需要访问r上的其他表,则不可能,因为它被用作函数而不是表达式.我可以将DeferredLoadingEnabled设置为true,但这会产生大量单个查询,而不是修改主查询以与所需的任何表连接.
有谁知道在将来的.NET版本中这些操作是否会变得更容易,或者我是否会以错误的方式解决这个问题?我非常喜欢Linq和Expression功能,我希望我可以使用更易读的代码来使用它们.
更新
以为我可能会添加一些示例,说明我如何使表达式更具组合性.他们并不简洁,但他们完成了工作.
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;
});
}
Run Code Online (Sandbox Code Playgroud)
那我在这做什么?请注意,在此版本的MapUser中,我没有正确创建ModResource对象,我只是创建一个虚拟版本.然后,我调用一个表达式访问器方法,该方法查找虚拟调用并将其替换为我最初想要的那个.对我而言,似乎缺少表达式语法,因为我能够基本上构造我最初想要的表达式树,但我必须实际上使用树来完成它.以下是我发现的另一种解决方法:处理奇异案例:
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);
}
Run Code Online (Sandbox Code Playgroud)
在第二个例子中,我知道我可以从用户数据中获取资源数据,但我无法"内联"表达式以显示如何执行此操作并将资源数据映射到资源POCO.但是我可以手动创建一个表达式树,该表达式给定已经映射的资源POCO并使用它.然后,我可以创建另一个表达式,该表达式显示如何从用户获取资源原始数据,以及最终表达式,该表达式显示如何将原始资源数据映射到资源POCO.现在可以想象,我可以将所有这些信息组合成单个表达式树,其方式是"折叠"资源特定参数,因为我可以从主用户参数获取它.这就是上面的代码所做的.
所以我找到了使表达式具有高度可组合性的方法......它只是感觉不干净.
我认为如果你想使用 POCO,Linq to SQL 并不是最好的选择。我认为使用 NHibernate 之类的东西可能会更好。将 Linq to SQL 与 POCO 结合使用意味着您要在数据库之上的数据层 (Linq to SQL) 之上构建一个层。使用 NHibernate,您将构建所有代码并将其直接映射到数据库。更少的层==更少的代码==更少的工作。
| 归档时间: |
|
| 查看次数: |
1082 次 |
| 最近记录: |