.net - 封装 Linq to SQL 数据访问的最佳方法是什么?

标签 .net linq-to-sql design-patterns lambda expression-trees

我一直在尝试将对象映射封装在项目数据存储库中。也许 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 { ...

代码将失败,因为我无法调用 MapResource 表达式,因为我试图从另一个表达式中调用它。我设法通过用 u => new ModResource() 替换“MapResource”来解决这个问题,然后使用 ExpressionVisitor 找到这个占位符节点并将其替换为 MapResource 表达式。

当我尝试使用涉及单个属性的表达式(即 UserResource = MapResource)分配 ModUser 的属性时,我也遇到了类似的问题。通过使用 Expression 类上的方法手动组合所需的表达式,我设法解决了第二个问题。

我确实意识到我可以将上面的代码更改为
UserResources = u.Resources(r => MapResource.Compile().Invoke(r));

但是最终生成的 SQL 查询将需要获取 r 的所有属性,而不仅仅是 MapResouce 所需的属性,因为我们现在正在处理一个函数。此外,如果 MapResouce 需要访问 r 上的更多表,这是不可能的,因为它被用作函数而不是表达式。我可以将 DeferredLoadingEnabled 设置为 true,但这会产生大量单独的查询,而不是修改主查询以加入所需的任何表。

有谁知道这些操作是否会在 future 的 .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;
    });
}

那我在这里做什么?请注意,在此版本的 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);
}

在第二个示例中,我知道我可以从用户数据中获取资源数据,但我不能“内联”表达式来展示如何执行此操作并将资源数据映射到资源 POCO。但是我可以手动创建一个表达式树,它给出了一个已经映射的资源 POCO 并使用它。然后,我可以创建另一个表达式来显示如何从用户那里获取资源原始数据,并创建一个最终表达式来显示如何将原始资源数据映射到资源 POCO。现在可以想象,我可以将所有这些信息组合到一个表达式树中,以一种“折叠”出资源特定参数的方式,因为我可以从主要用户参数中获取它。这就是上面的代码所做的。

所以我找到了使表达式高度可组合的方法......它只是感觉不干净。

最佳答案

Linq To SQL 支持 POCO 的方式有点不同。

要实现持久性无知,您将使用描述 modUser 如何映射(列、关联等)的映射文件,而不是 LTS 设计器。
创建新上下文时,将 XML 映射文件作为 XMLMappingSource 传递给它。

这样,LTS 将从数据库中返回您的对象。

我在这里和那里读过,将您的集合关联属性定义为 IList(of T) 类型的读/写属性足以让 LinqToSQL 在这些集合上提供延迟加载,但我没有尝试过,所以我不能保证它.

Entity Framework 在其当前版本中对 POCO 的支持会更糟(就大多数人对 POCO 一词的理解而言,基本上没有)。

所有常见的 LTS 限制都适用于此,因此没有“值对象”映射。如果您想从数据库和 POCO 支持中删除一些更远的东西,那么您需要查看 NHibernate。

关于.net - 封装 Linq to SQL 数据访问的最佳方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/597702/

相关文章:

c# - 使用语法编写测试数据生成器

c# - 如何获取 IEnumerable<object> 的 linq 总和

java - 计算类的实例

.net - 在 NHibernate 中按公式字段排序

c# - 跨多个数据库的 DataContext

sql-server - LINQ to SQL 不跳过导致多个 SQL 语句

Linq To Sql - 从日期获取星期几名称

c# - 您能否以编程方式检测 Excel 文档中的文本颜色并仅修改具有特定颜色和字体样式的文本的单元格?

java - 如何使用 Query、QueryBuilder 和 Test 类摆脱在 java 项目输出中打印对象内存位置?

design-patterns - 如何在社交网络中实现事件流