Стратегии запросов NHibernate в веб-приложениях - PullRequest
6 голосов
/ 22 марта 2012

В сценариях веб-приложений обычно существует два требования к поиску данных: 1. Все запросы должны быть опционально 2. Существует множество «фильтрующих» запросов, необходимых пользователю (например, поиск человека по имени, почте и возрасту) 3. Сортировка запросов, подразумеваемых некоторыми клиентскими сетками

И, конечно, комбинированный случай: отфильтрованные, разбитые на страницы запросы, которые сортируются:)

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

Мне известно о том, что методы хранилища должны быть чистыми и четко определенными. Но в данный момент мне хочется писать много шаблонного кода всякий раз, когда я добавляю какую-то форму фильтрации в MVC-представление или какую-то сортируемую страничную таблицу.

Как другие разработчики справляются с такими требованиями?

Заранее спасибо

Ответы [ 5 ]

7 голосов
/ 22 марта 2012

Репозитории, универсальные репозитории и репозитории, которые предоставляют IQueryable, являются очень острыми спорами.

Суть в том, что репозиторий является универсальным или представляет собой IQueryable вообще не является реальным репозиторием, это просто абстракция вашего уровня данных.

Теперь это неплохо иметь, но не называйте это хранилищем, называйте это так, как оно есть. Абстракция уровня данных позволяет вам быстро вставить что-то в ваш пользовательский интерфейс, где вы читаете и записываете сущности, не пропуская свою структуру уровня данных в пользовательский интерфейс. Конечно, вы можете просто ввести ISession и покончить с этим.

public interface IRepository<T> {}

public class NHibernateRepository<T> : IRepository<T>
{
    private ISession session;

    public NHibernateRepository(ISession session)
    {
        this.session = session;
    }

    T Get(object id) { return session.GetById<T>(id)); }

    IQueryable<T> Query { get { return session.Query<T>(); }
}

new NHibernateRepository<Customer>(session).Query().Where(customer => customer.Name == Fred);

Однако, если вы хотите захватить некоторую повторно используемую логику и предоставить четкий контракт между вашими службами или пользовательским интерфейсом и вашим уровнем данных, тогда Репозиторий сделает именно это. Вы определяете четкие методы, которые говорят, что он получает и как. Кроме того, с репозиторием вам нужно только выставить свои агрегатные корни , это корневые объекты, к которым привязаны все ваши другие данные, это могут быть такие вещи, как Customer и Supplier. Вы не пытаетесь получить прямой адрес, вы загружаете клиента и затем запрашиваете его адреса. Вы бы загружали список поставщиков, основываясь на том, что они поставляют, но вы не пошли бы через «ItemsRepository». Мои примеры могут быть не самыми лучшими, но они дают вам представление.

public class CustomerRepository
{
    public Customer GetCustomerWithName(string name);
}

public class SupplierRepository
{
    public IEnumerable<Supplier> GetSuppliersWhoStockItem(string itemName)
}

Наконец, если вы любите приключения, вы, возможно, захотите взглянуть на CQRS , это большая тема для обрисовки, но есть множество примеров.

Первое быстрее реализуется, второе дает вам более понятный повторно используемый код, третье дает вам разделение между вашим уровнем пользовательского интерфейса, но требует дополнительных усилий. Это зависит от того, что вам нужно и чего вы хотите, и, вероятно, должны решаться в таком порядке.

3 голосов
/ 22 марта 2012

Вы используете репозиторий для перечисления совокупного корня. Довольно часто ваши контроллеры работают с объединенным корнем, который вы можете фильтровать, сортировать и т. Д. По требованию пользователя.

Поэтому я использую хранилище в соответствии с тем, что уже было упомянуто.

Однако иногда мне нужно работать в более сложных спецификациях, где использование совокупного корня и / или целой группы хранилищ является либо болезненным, неэффективным, либо просто невозможным. Например, вам может потребоваться запустить большой бизнес-отчет или выполнить пакетную команду.

В подобных случаях я также определил ICommand / IQuery с базовой реализацией NH, чтобы заботиться о сантехнике (как это делает обобщенный Repository).

То, что я тогда делаю, - это создаю интерфейс, который представляет контракт для спецификации, выставляя всех членов, которые мне могут понадобиться, чтобы помочь мне построить необходимые параметры. Затем я делаю реализацию этой спецификации, используя NH в качестве основы, выполняя спецификацию, используя любую технику, которая наиболее подходит (оператор HQL, необработанный SQL, Criteria, QueryOver ... что угодно).

Вот грубая иллюстрация того, что я имею в виду. Обратите внимание, что я использую произвольный ICommandProvider, который представляет собой некоторый объект, который создает новые экземпляры команды по мере необходимости (в случае, если вам нужно выполнить несколько команд за одну операцию). Я регистрировал свои команды в IoC, и провайдер работал с ним для создания экземпляров команд.

public interface ICommand
{

}

public interface ICommandProvider
{
    TCommand Create<TCommand>()
        where TCommand : ICommand;

}

public interface IQuery<TResult> : ICommand
{
    TResult Execute();
}

public class NhCommand : ICommand
{
    // plumbing stuff here, like finding the current session
}

public class DelinquentAccountViewModel
{
    public string AccountName { get; set; }
    public decimal Amount { get; set; }
}

public interface IDelinquentAccountsQuery : IQuery<IEnumerable<DelinquentAccountViewModel>>
{
    void AmountGreaterThan(decimal amount);
    // you could define members for specifying sorting, etc. here
}

public class DelinquentAccountsQuery : NhCommand
{
    public IEnumerable<DelinquentAccountViewModel> Execute()
    {
        // build HQL and execute results, resulting in a list of DelinquentAccountViewModels
        // using _amountGreaterThan as a parameter
        return null;
    }

    private Decimal _amountGreaterThan;

    public void AmountGreaterThan(Decimal amount)
    {
        _amountGreaterThan = amount;
    }
}

Использование в контроллере может выглядеть примерно так:

public class DelinquentAccountsController : Controller
{
     protected ICommandProvider CommandProvider { get; private set; }

     public DelinquentAccountsController(ICommandProvider commandProvider)
     {
         CommandProvider = commandProvider;
     }

     public ActionResult Index(decimal amount)
     {
         var query = CommandProvider.Create<IDelinquentAccountsQuery>();
         query.AmountGreaterThan(amount);
         return View(query.Execute());

     }
}

Ничто не говорит о том, что вы не можете получить доступ ко всем данным с помощью команды / запроса, но это больше работы, чем мне нужно. Я считаю, что стандартный подход к хранилищу (с использованием LINQ против NHibernate) обеспечивает примерно 95% доступа к данным, который требуется моим приложениям.

3 голосов
/ 22 марта 2012

В нашем проекте мы также использовали репозитории, репозиторий для каждой сущности, но мне это не нравится.Это может создать много проблем, когда вы пишете сложные запросы с большим количеством взаимодействующих объектов.Я думаю, что это будет один общий репозиторий для базовых операций, и все запросы должны присутствовать с шаблоном Query Object, что-то вроде отдельного класса для запроса, хотя посмотрите на:

http://richarddingwall.name/2010/06/15/brownfield-cqrs-part-1-commands/

http://codebetter.com/gregyoung/2009/01/20/ddd-specification-or-query-object/

http://devlicio.us/blogs/casey/archive/2009/02/13/ddd-command-query-separation-as-an-architectural-concept.aspx

http://blog.jonathanoliver.com/2009/10/dddd-why-i-love-cqrs/

http://www.udidahan.com/2007/03/28/query-objects-vs-methods-on-a-repository/

1 голос
/ 22 марта 2012

Imho orm уже достаточно абстракции. Кроме того, вам не нужны репозитории. Вам нужна только абстракция, если вы собираетесь изменить orm на лету с некоторыми настройками. Где, Skip, Take, OrderBy и т. Д. Являются независимыми от orm, и их можно использовать, выставляя IQueryable. Но некоторые функции специфичны для orm (например, fetch vs. include), и они делают репозитории действительно ужасными (миллион методов или метод с миллионами параметров)

Я обычно просто делаю методы расширения

public static class QueryExtensions
{
    public static IQueryable<T> Published(this IQueryable<T> pages) where T : IPage
    {
        return pages.Where(p => p.State == PageState.Public && p.Published <= DateTime.UtcNow);
    }
    public static IQueryable<T> By(this IQueryable<T> pages, User author) where T : IPage
    {
        return pages.Where(p => p.Author == author);
    }

    public static IEnumerable<Foo> AdvancedThing(this ISession session, string text)
    {
        // I get all the power of NHibernate :)
        return session.CreateQuery("...").SetString("text", text).List<Foo>();
    }
}

и использовать ISession напрямую в методах действия

var posts = session.Query<Post>().By(user).Published().FetchMany(p => p.Tags).ToList();
1 голос
/ 22 марта 2012

Вы можете заставить свой репозиторий предоставлять IQueryable и позволить ActionMethod выяснить, что отображать. Например:

    public System.Linq.IQueryable<Models.MyModel> Query()
    {
        return mSession.Query<Models.MyModel>();
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...