Как я могу реорганизовать мой LinqToSql, чтобы сделать его более понятным? - PullRequest
3 голосов
/ 19 октября 2011

Я использую 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 (и несколько других) всякий раз, когда я повторяю результат и отображаю его.

Sp что я могу сделать здесь?Я не боюсь вносить радикальные изменения в случае необходимости.

Ответы [ 3 ]

4 голосов
/ 19 октября 2011

Для начала, если ваши запросы относительно просты, я бы не стал создавать набор вспомогательных методов доступа к данным, таких как GetNewest(), GetOldest(), GetForUser(int), 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;
}

Это позволило быкод вызова:

var postViewModelsInCategoriesStartingWithN =
    db.Posts
        .GetByCategories(db.Categories.WhereStartsWith("N"))
        .WhereStatusVisible()
        .ToPostViewModels(db.Users);

Итак, суть такого подхода в том, что вы:

  1. предоставляете атомарные, составные операторы, которые действуют на IQueryable<T> и переходят на IQueryable<T>.
  2. используйте IQueryable<> в базе данных и переходите к IEnumerable<> при работе с классами, не относящимися к базе данных.
  3. избегайте того, чтобы ваш код вызова зависел от особенностей базы данных (т.е. неint, User или IQueryable<User>).

Если есть что-то еще, дайте мне знать.Надеюсь, это поможет.

1 голос
/ 19 октября 2011

Например, вы можете создать набор методов расширения, помогающих вам выполнять общие задачи, такие как:

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.поэтому в большинстве запросов у вас есть что-то вроде: order by p.Name.А что если вдруг в поле Имя добавится Фамилия?вместо того, чтобы сортировать только по полю имени, теперь вам нужно отсортировать по 2 полям.Однако, если вы создали расширение Query в начале, например:

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

Вам нужно только изменить реализацию OrderByName, и каждый метод, использующий это, будет автоматически затронут.(Это вызывается многими шаблонами конвейера).

0 голосов
/ 19 октября 2011

Вот один из способов:

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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...