我怎样才能重构我的LinqToSql以使其更易于维护?

时间:2011-10-19 01:51:20

标签: c# linq-to-sql refactoring

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

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

  • GetNewest()
  • GetOldest()
  • GetForUser(int)的
  • GetByCategory(int)的

每个方法都返回一个名为PostViewModel的模型。它是帖子的扩展版本,以及它的一些外键。 (作者姓名等......)

帖子如下:

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
}

我觉得我做错了什么。如果我对PostViewModel进行更改,我将不得不更改每个方法。可能需要注意的是,我将对象扩展为PostViewModel,因此每当我迭代结果并显示它时,我都不必返回数据库来获取AuthorName(以及其他一些)。

我可以在这做什么?如果需要的话,我不怕做出重大改变。

3 个答案:

答案 0 :(得分:4)

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

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

GetNewest()

但后来包括public IQueryable<Post> GetNewest() { return from p in db.Posts orderby p.DateCreated descending select p; } 子句如下:

where p.StatusID == (int)PostStatus.Visible

现在,所有调用代码仍然显示为public IQueryable<Post> GetNewest() { return from p in db.Posts where p.StatusID == (int)PostStatus.Visible orderby p.DateCreated descending select p; } ,所以只需查看调用代码,您就不知道是否可见,或者排序是什么。事实上,一些现有的代码可能会期望GetNewest()返回不可见的帖子,现在你已经破坏了代码而没有意识到它。

另一方面,您不希望在整个代码中查询都很简单。如果您更改了数据库架构,那么可能会有很多损坏的代码。

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

所以这就是我建议你为数据访问代码做的事情:

GetNewest()

现在你可以编写这种调用代码:

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

这变得非常清楚正在发生的事情,并且这些扩展方法的底层实现不可能发生变化,并且所有模式细节都隐藏在扩展方法中。

然后,您可以对此进行扩展以处理视图模型的创建。既然视图模型不是数据库的一部分,我会从var query = db.Posts .WhereStatusVisible() .OrderByDateCreatedDescending(); 转到IQueryable<Post>,因为看起来你需要加入作者(大概来自IEnumerable<PostViewModel>表)我会做这样的事情:

User

现在调用代码如下所示:

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

所以这种方法的底线是你:

  1. 提供符合var postViewModelsInCategoriesStartingWithN = db.Posts .GetByCategories(db.Categories.WhereStartsWith("N")) .WhereStatusVisible() .ToPostViewModels(db.Users); IQueryable<T>行事的原子,可组合运算符。
  2. 在数据库中使用IQueryable<T>,在使用非数据库类时转到IQueryable<>
  3. 避免让您的调用代码依赖于数据库细节(即不要IEnumerable<>intUser这样做。
  4. 如果有任何进一步让我知道。我希望这会有所帮助。

答案 1 :(得分:1)

首先,您可以创建一组帮助您执行常见任务的扩展方法,例如:

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

您可以随意添加任何类型的扩展方法。包括排序,过滤等。因此可以在查询扩展中转换重复的任务。

如果你害怕你的模型将来可能会改变(或要求),你甚至可以为每个简单的基本任务扩展它。让我们举一个例子:要求是大多数方法按PosterName排序。所以在大多数查询中你都有类似的东西:按p.Name排序。现在如果突然使用LastName扩展Name字段。您现在必须对2个字段进行排序,而不必仅在“名称”字段上进行排序。但是,如果您在开头创建了一个Query扩展名,如:

public static IQueryable<Post> OrderByName(this IQueryable<Post> query) { ... }

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

答案 2 :(得分:0)

这是一种方式:

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