Как правильно использовать шаблон хранилища?
Мне интересно, как мне группировать свои репозитории? Как из примеров, которые я видел на asp.net mvc, и в моих книгах они в основном используют один репозиторий для каждой таблицы базы данных. Но это похоже на множество репозиториев, заставляющих вас впоследствии вызвать много репозиториев для насмешек и т.д.
Итак, я предполагаю, что я должен их сгруппировать. Однако я не уверен, как их группировать.
В настоящий момент я создал регистрационный репозиторий для обработки всех моих регистрационных материалов. Однако есть 4 таблицы, которые мне нужно обновить, и до того, как у меня было 3 репозитория для этого.
Например, одна из таблиц - это таблица лицензий. Когда они регистрируются, я просматриваю их ключ и проверяю его, чтобы увидеть, существует ли в базе данных. Теперь, что произойдет, если мне нужно проверить этот лицензионный ключ или что-то еще из этой таблицы в другом месте, кроме регистрации?
Одно место может быть логином (проверьте, не истекает ли ключ).
Итак, что бы я сделал в этой ситуации? Перепишите код еще раз (перерыв DRY)? Попытайтесь объединить эти 2 репозитория вместе и надеяться, что ни один из методов не понадобится в какой-то другой момент времени (например, возможно, у меня может быть метод, который проверяет, используется ли имя пользователя), возможно, мне понадобится это где-то еще).
Кроме того, если я объединю их вместе, мне потребуется либо 2 уровня обслуживания, идущих в один и тот же репозиторий, так как я думаю, что вся логика для двух разных частей сайта будет длинной, и мне нужно будет иметь имена типа ValidateLogin(), ValdiateRegistrationForm(), ValdiateLoginRetrievePassword() и т.д.
Или вызывать репозиторий в любом случае и просто иметь странное звучание?
Мне кажется, сложно создать репозиторий с достаточно общим именем, чтобы вы могли использовать его во многих местах вашего приложения и по-прежнему иметь смысл, и я не думаю, что вызов другого репозитория в репозитории будет хорошей практикой?
Ответы
Ответ 1
Одна вещь, которую я сделал неправильно, когда играл с шаблоном репозитория - точно так же, как вы, я думал, что таблица относится к репозиторию 1:1. Когда мы применяем некоторые правила из Domain Driven Design - проблема с группировкой репозиториев часто исчезает.
Репозиторий должен быть за Агрегировать root, а не таблицу. Это означает, что если объект не должен жить один (т.е. Если у вас есть Registrant
, в котором участвует, в частности, Registration
) - это просто сущность, ему не нужен репозиторий, он должен быть обновлен/создан/получен через репозиторий совокупного корня он принадлежит.
Конечно, во многих случаях эта методика сокращения количества репозиториев (на самом деле - это скорее метод структурирования вашей модели домена) не может применяться, поскольку каждый объект должен быть совокупным корнем (который сильно зависит от ваш домен, я могу предоставить только слепые догадки). В вашем примере - License
представляется сводным корнем, потому что вы должны иметь возможность проверять их без контекста объекта Registration
.
Но это не ограничивает нас каскадными репозиториями (репозиторий Registration
разрешен при необходимости ссылаться на репозиторий License
). Это не ограничивает нас ссылкой License
репозитория (предпочтительно - через IoC) непосредственно из объекта Registration
.
Просто старайтесь не сводить свой дизайн с помощью усложнений, предоставляемых технологиями, или что-то не понимаете. Группировка репозиториев в ServiceX
только потому, что вы не хотите создавать 2 репозитория, это не очень хорошая идея.
Гораздо лучше было бы дать ему собственное имя - RegistrationService
i.e.
Но услуг вообще следует избегать - часто это приводит к тому, что приводит к модели анемичного домена.
EDIT:
Начните использовать IoC. Это действительно облегчает боль от инъекционных зависимостей.
Вместо написания:
var registrationService = new RegistrationService(new RegistrationRepository(),
new LicenseRepository(), new GodOnlyKnowsWhatElseThatServiceNeeds());
вы сможете написать:
var registrationService = IoC.Resolve<IRegistrationService>();
P.s. Было бы лучше использовать так называемый общий локатор сервисов, но это просто пример.
Ответ 2
Одна вещь, которую я начал делать, чтобы решить эту проблему, - это фактически разработать службы, которые обертывают N репозиториев. Надеемся, что ваши рамки DI или IoC помогут сделать это проще.
public class ServiceImpl {
public ServiceImpl(IRepo1 repo1, IRepo2 repo2...) { }
}
Это имеет смысл? Кроме того, я понимаю, что говоря об услугах в этой усадьбе может или не может фактически соответствовать принципам DDD, я просто делаю это, потому что это работает.
Ответ 3
То, что я делаю, это абстрактный базовый класс, который определяется следующим образом:
public abstract class ReadOnlyRepository<T,V>
{
V Find(T lookupKey);
}
public abstract class InsertRepository<T>
{
void Add(T entityToSave);
}
public abstract class UpdateRepository<T,V>
{
V Update(T entityToUpdate);
}
public abstract class DeleteRepository<T>
{
void Delete(T entityToDelete);
}
Затем вы можете получить репозиторий из абстрактного базового класса и расширить свой единственный репозиторий до тех пор, пока общие аргументы не будут отличаться, например:
public class RegistrationRepository: ReadOnlyRepository<int, IRegistrationItem>,
ReadOnlyRepository<string, IRegistrationItem>
и т.д....
Мне нужны отдельные репозитории, потому что у нас есть ограничения на некоторые из наших репозиториев, и это дает нам максимальную гибкость. Надеюсь, это поможет.
Ответ 4
У меня это как мой класс репозитория, и да, я расширяюсь в репозитории таблицы/области, но мне иногда приходится прерывать DRY.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MvcRepository
{
public class Repository<T> : IRepository<T> where T : class
{
protected System.Data.Linq.DataContext _dataContextFactory;
public IQueryable<T> All()
{
return GetTable.AsQueryable();
}
public IQueryable<T> FindAll(Func<T, bool> exp)
{
return GetTable.Where<T>(exp).AsQueryable();
}
public T Single(Func<T, bool> exp)
{
return GetTable.Single(exp);
}
public virtual void MarkForDeletion(T entity)
{
_dataContextFactory.GetTable<T>().DeleteOnSubmit(entity);
}
public virtual T CreateInstance()
{
T entity = Activator.CreateInstance<T>();
GetTable.InsertOnSubmit(entity);
return entity;
}
public void SaveAll()
{
_dataContextFactory.SubmitChanges();
}
public Repository(System.Data.Linq.DataContext dataContextFactory)
{
_dataContextFactory = dataContextFactory;
}
public System.Data.Linq.Table<T> GetTable
{
get { return _dataContextFactory.GetTable<T>(); }
}
}
}
ИЗМЕНИТЬ
public class AdminRepository<T> : Repository<T> where T: class
{
static AdminDataContext dc = new AdminDataContext(System.Configuration.ConfigurationManager.ConnectionStrings["MY_ConnectionString"].ConnectionString);
public AdminRepository()
: base( dc )
{
}
У меня также есть datacontext, который был создан с использованием класса Linq2SQL.dbml.
Итак, теперь у меня есть стандартный репозиторий, реализующий стандартные вызовы, такие как "Все" и "Найти", и в моем AdminRepository у меня есть специальные вызовы.
Не отвечает на вопрос о СУШЕ, хотя я не думаю.
Ответ 5
Вот пример общей реализации Репозитория с использованием FluentNHibernate. Он способен сохранять любой класс, на который вы написали карту. Он даже способен генерировать вашу базу данных на основе классов сопоставления.
Ответ 6
Паттерн репозитория - плохой шаблон проектирования. Я работаю со многими старыми проектами .Net, и этот шаблон обычно приводит к ошибкам "Распределенные транзакции", "Частичный откат" и "Удаленный пул подключений", которых можно было бы избежать. Проблема в том, что шаблон пытается обрабатывать соединения и транзакции внутренне, но они должны обрабатываться на уровне контроллера. Также EntityFramework уже абстрагирует много логики. Я бы предложил использовать шаблон Service вместо повторного использования общего кода.
Ответ 7
Я предлагаю вам посмотреть Sharp Architecture. Они предлагают использовать один репозиторий для каждого объекта. Я использую его в настоящее время в своем проекте и очень доволен результатами.