将Linq封装到SQL数据访问的最佳方法是什么?

时间:2009-02-28 07:38:36

标签: .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 =&gt;替换'MapResource'来解决这个问题。新的ModResource(),然后使用ExpressionVisitor查找此占位符节点并将其替换为MapResource表达式。

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

我意识到我可以将上面的代码更改为

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

然后生成的最终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;
    });
}

那我在这做什么?请注意,在此版本的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。现在可以想象,我可以将所有这些信息组合成单个表达式树,其方式是“折叠”资源特定参数,因为我可以从主用户参数获取它。这就是上面代码的作用。

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

3 个答案:

答案 0 :(得分:1)

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

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

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

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

实体框架对于当前版本的POCO支持将更加糟糕(基本上没有人能够理解POCO一词)。

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

答案 1 :(得分:1)

好的我不得不承认我没有真正读完OP的问题(羞怯的笑),但你知道你可以使用Linq-to-SQL属性来装饰任何POCO对象吗?您不必使用设计师。

这是一个代码的随机示例,现在在我面前展开。这是一个名为“Product”的POCO,它有一些应用于它的属性,可以让它与Linq-to-SQL DataContext进行交互。

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; }

    }
}

答案 2 :(得分:0)

我认为如果你想使用POCO,Linq to SQL不是最好的选择。我觉得使用像NHibernate这样的东西可能要好得多。将Linq to SQL与POCO一起使用意味着您要在数据库之上的数据层(Linq to SQL)之上构建一个层。使用NHibernate,您将构建所有代码并将其直接映射到数据库。较少的图层==较少的代码==较少的工作。