C# 如何重构LinqToSql以使其更易于维护?

C# 如何重构LinqToSql以使其更易于维护?,c#,linq-to-sql,refactoring,C#,Linq To Sql,Refactoring,我使用Linq2Sql作为数据访问,但我很快就注意到我做错了什么 我有一个帖子列表,我需要能够以几种不同的方式过滤它。例如: getlatest() GetOlests() GetForUser(int) GetByCategory(int) 每个方法都返回一个我调用的模型PostViewModel。它是post的扩展版本,以及它的一些外键。(作者姓名等) 一个帖子看起来像: class Post { int Id; DateTime DateCreated; int

我使用Linq2Sql作为数据访问,但我很快就注意到我做错了什么

我有一个帖子列表,我需要能够以几种不同的方式过滤它。例如:

  • getlatest()
  • GetOlests()
  • GetForUser(int)
  • GetByCategory(int)
每个方法都返回一个我调用的模型
PostViewModel
。它是post的扩展版本,以及它的一些外键。(作者姓名等)

一个帖子看起来像:

class Post
{
    int Id;
    DateTime DateCreated;
    int AuthorId;
}
PostViewModel将是:

class PostViewModel
{
    int Id;
    DateTime DateCreated;
    string AuthorName;
}
上面的每个方法都类似于

from p in db.Posts
where p.StatusID == (int)PostStatus.Visible //this can be different for each
order by <something>  //this can be different for each
select new PostViewModel() //this is the same for each
{
    //snip
}
从p到db.Posts
其中p.StatusID==(int)PostStatus.Visible//对于每个
订购人//这对于每个人都可能不同
选择new PostViewModel()//这对于每个
{
//剪断
}
我觉得我做错了什么。如果对PostViewModel进行更改,则必须更改其中的每一种方法。可能需要注意的是,我将对象扩展到PostViewModel中,这样每当我迭代结果并显示它时,就不必返回数据库获取AuthorName(以及其他一些)

Sp我能在这里做什么?如果需要,我不怕做出剧烈的改变。

这里有一种方法:

public enum PostType
{
    Oldest,
    Newest,
    ForUser,
    ByCat
}
public List<PostViewModel> GetPosts(PostType pt, YourDataContext db, int UserId = 0)
{
    List<PostViewModel> v = db.Posts.Select(i => new PostViewModel() { /* snip */});
    switch(pt)
    {
        case pt.Oldest:
            v = v.Where(i => p.StatusID == (int)PostStatus.Visible).OrderDescendingBy(i => i.DateCreated).ToList();
            break;
        case pt.ByUser:
            v = v.Where(i => p.UserId == UserId).ToList();
            break;
           ...

    }   
    return v;
}
公共枚举后类型
{
最老的,
最新的,
对于用户,
拜卡特
}
公共列表GetPosts(PostType pt,YourDataContext db,int UserId=0)
{
List v=db.Posts.Select(i=>newpostViewModel(){/*snip*/});
开关(pt)
{
案例部分:
v=v.Where(i=>p.StatusID==(int)PostStatus.Visible).orderDenseringBy(i=>i.DateCreated).ToList();
打破
案例pt.ByUser:
v=v.Where(i=>p.UserId==UserId).ToList();
打破
...
}   
返回v;
}

首先,您可以创建一组扩展方法来帮助您完成常见任务,如:

static class PostQueryExtensions
{
    public static IQueryable<PostViewModel> Materialize(this IQueryable<Post> query)
    {
        return from post in query
               select new PostViewModel()
               {
                   ....
               }
    }
}

class PostDataAccess
{
    public IEnumerable<PostViewModel> GetOldest()
    {
        var query = db.Posts
                      .OrderBy(x => x.DateCreated)
                      .Materialize();
    }

    public IEnumerable<PostViewModel> GetNewest()
    {
        var query = db.Posts
                      .OrderByDescending(x => x.DateCreated)
                      .Materialize();
    }
}
静态类PostQueryExtensions
{
公共静态IQueryable具体化(此IQueryable查询)
{
在查询中从post返回
选择新的PostViewModel()
{
....
}
}
}
类PostDataAccess
{
公共IEnumerable getOlests()
{
var query=db.Posts
.OrderBy(x=>x.DateCreated)
.物化();
}
公共IEnumerable getlatest()
{
var query=db.Posts
.OrderByDescending(x=>x.DateCreated)
.物化();
}
}
您可以自由地包含任何类型的扩展方法。包括排序、筛选等,因此可以在查询扩展中转换重复任务

如果您担心您的模型将来可能会改变(或需求),您甚至可以为每个简单的基本任务扩展它。让我们举个例子:要求大多数方法按PosterName排序。因此,在大多数查询中,都有类似的内容:orderbyp.Name。现在,如果名称字段突然扩展为LastName,该怎么办。现在,您不必只对名称字段进行排序,而必须对2个字段进行排序。但是,如果您在开始时进行了查询扩展,如:

public static IQueryable<Post> OrderByName(this IQueryable<Post> query) { ... }
public静态IQueryable OrderByName(此IQueryable查询){…}

您只需修改OrderByName的实现,使用它的每个方法都将自动受到影响。(这被许多管道模式称为)。

首先,如果您的查询相对简单,我倾向于避免创建一组数据访问帮助器方法,如
GetNewest()
getolest()
GetForUser(int)
GetByCategory(int)
,这类方法可能会适得其反,因为它们隐藏了对调用代码可能很重要的底层细节,并且使组合查询变得困难

例如,您可以像这样实现
getlatest()

public IQueryable<Post> GetNewest()
{
    return
        from p in db.Posts
        orderby p.DateCreated descending
        select p;
}
public static IEnumerable<PostViewModel> ToPostViewModels(
    this IQueryable<Post> posts,
    IQueryable<User> users)
{
    var query =
        from p in posts
        join u in users on p.AuthorId equals u.Id
        select new { p, u };

    return
        query
            .ToArray()
            .Select(q => new PostViewModel()
            {
                Id = q.p.Id,
                DateCreated = q.p.DateCreated,
                AuthorName = q.u.Name,
            })
            .ToArray();
}
var postViewModels =
    db.Posts
        .WhereStatusVisible()
        .OrderByDateCreatedDescending()
        .ToPostViewModels(db.Users);
现在,所有的调用代码仍然读作
getlatest()
,因此,只要查看调用代码,您就不知道是否可见,或者顺序是什么。事实上,一些现有的代码可能期望
getlatest()
返回不可见的帖子,而现在您在没有意识到的情况下破坏了这些代码

但是,另一方面,您不希望查询在整个代码中都是一知半解的。如果您更改了数据库模式,那么可能会有很多错误的代码

正确的方法是将查询看作是一系列可组合的原子操作

因此,我建议您对数据访问代码执行以下操作:

public static IQueryable<Post> WhereStatusVisible(
    this IQueryable<Post> posts)
{
    return
        from p in posts
        where p.StatusID == (int)PostStatus.Visible
        select p;
}

public static IQueryable<Post> OrderByDateCreatedDescending(
    this IQueryable<Post> posts)
{
    return
        from p in posts
        orderby p.DateCreated descending
        select p;
}
这就非常清楚正在发生什么,并且这些扩展方法的底层实现不可能改变,所有的模式细节都隐藏在扩展方法中

然后,您可以扩展它来处理视图模型的创建。现在,由于视图模型不是您数据库的一部分,我将从
IQueryable
转到
IEnumerable
,并且由于您似乎需要加入作者(可能来自
用户
表),我将这样做:

public IQueryable<Post> GetNewest()
{
    return
        from p in db.Posts
        orderby p.DateCreated descending
        select p;
}
public static IEnumerable<PostViewModel> ToPostViewModels(
    this IQueryable<Post> posts,
    IQueryable<User> users)
{
    var query =
        from p in posts
        join u in users on p.AuthorId equals u.Id
        select new { p, u };

    return
        query
            .ToArray()
            .Select(q => new PostViewModel()
            {
                Id = q.p.Id,
                DateCreated = q.p.DateCreated,
                AuthorName = q.u.Name,
            })
            .ToArray();
}
var postViewModels =
    db.Posts
        .WhereStatusVisible()
        .OrderByDateCreatedDescending()
        .ToPostViewModels(db.Users);
然后我也会考虑编写这种代码:

public static IQueryable<Post> GetByCategories(
    this IQueryable<Post> posts,
    IQueryable<Category> categories)
{
    return
        from p in posts
        join c in categories on p.CategoryId equals c.Id
        select p;
}

public static IQueryable<Category> WhereStartsWith(
    this IQueryable<Category> categories,
    string startsWith)
{
    return
        from c in categories
        where c.Name.StartsWith(startsWith)
        select c;
}
因此,这种方法的底线是:

  • 提供原子的、可组合的运算符,这些运算符作用于
    IQueryable
    转到
    IQueryable
  • 在数据库中使用
    IQueryable
    ,在处理非数据库类时转到
    IEnumerable
  • 避免使调用代码依赖于数据库特定信息(即,不要通过
    int
    、通过
    User
    IQueryable
    获取)

  • 如果有任何进一步的消息,请告诉我。我希望这能有所帮助。

    我只是想说声谢谢。进行了此更改,一切都变得完美,并进行了自文档化查询。看起来很简单。我不敢相信我没有想到:)@Joe-没有