Я знаю две вещи неясного выхода, которые делают их отличающимися. Пойдите меня!
Во-первых, существует классическая ошибка создания делегата к каждому объекту в списке. Если Вы используете foreach ключевое слово, все Ваши делегаты могут закончить тем, что обратились к последнему объекту списка:
// A list of actions to execute later
List actions = new List();
// Numbers 0 to 9
List numbers = Enumerable.Range(0, 10).ToList();
// Store an action that prints each number (WRONG!)
foreach (int number in numbers)
actions.Add(() => Console.WriteLine(number));
// Run the actions, we actually print 10 copies of "9"
foreach (Action action in actions)
action();
// So try again
actions.Clear();
// Store an action that prints each number (RIGHT!)
numbers.ForEach(number =>
actions.Add(() => Console.WriteLine(number)));
// Run the actions
foreach (Action action in actions)
action();
Список. Метод ForEach не имеет этой проблемы. Текущий объект повторения передается значением как аргумент внешней лямбде, и затем внутренняя лямбда правильно получает тот аргумент в своем собственном закрытии. Проблема решена.
(Печально я верю, ForEach является членом Списка, а не дополнительного метода, хотя легко определить его самостоятельно, таким образом, у Вас есть это средство на любом перечислимом типе.)
, Во-вторых, подход метода ForEach имеет ограничение. При реализации IEnumerable при помощи возврата урожая Вы не можете сделать возврата урожая в лямбде. Таким образом, цикличное выполнение через объекты в наборе для получения вещей возврата не возможно этим методом. Необходимо будет использовать foreach ключевое слово и работу вокруг проблемы закрытия путем ручного создания копии текущего значения цикла в цикле.
Если вы создаете свой репозиторий для конкретной сущности (таблицы), так что каждая сущность имеет список методов в вашем интерфейсе IRepository, который вы указали выше, тогда то, что вы действительно делаете является реализацией паттерна Active Record .
Вам не следует определенно иметь один репозиторий для каждой таблицы. Вам необходимо определить агрегаты в вашей модели предметной области и операции, которые вы хотите выполнить с ними. Пользователи и роли обычно тесно связаны, и, как правило, ваше приложение будет выполнять с ними операции в тандеме - для этого требуется единый репозиторий, сосредоточенный вокруг пользователя и набора тесно связанных сущностей.
Судя по вашему сообщению, вы видели этот пример . Проблема с этим примером заключается в том, что все репозитории используют одну и ту же функциональность CRUD на базовом уровне, но он не выходит за рамки этого и не реализует ни одну из функций домена. Все репозитории в этом примере выглядят одинаково, но на самом деле реальные репозитории не все выглядят одинаково (хотя они все равно должны иметь интерфейс), с каждым из них будут связаны определенные операции домена.
Операции домена вашего репозитория должен выглядеть примерно так:
userRepository.FindRolesByUserId(int userID)
userRepository.AddUserToRole(int userID)
userRepository.FindAllUsers()
userRepository.FindAllRoles()
userRepository.GetUserSettings(int userID)
и т.д ...
Это особые операции, которые ваше приложение хочет выполнять с базовыми данными, и Репозиторий должен их предоставлять. Думайте об этом как о репозитории, который представляет собой набор атомарных операций, которые вы должны выполнить в домене. Если вы решите поделиться некоторыми функциями через общий репозиторий, и расширять определенные репозитории с помощью методов расширения, это один из подходов, который может отлично работать для вашего приложения.
Хорошее практическое правило состоит в том, что редко вашему приложению необходимо создавать экземпляры нескольких репозиториев для завершить операцию. Необходимость действительно возникает, но если каждый обработчик событий в вашем приложении манипулирует шестью репозиториями только для того, чтобы принять ввод пользователя и правильно создать экземпляры сущностей, которые представляет ввод, то у вас, вероятно, возникнут проблемы с дизайном.
Является ли репозиторий на таблицу общим?
Нет, но у вас все еще может быть несколько репозиториев. Вы должны построить репозиторий вокруг агрегата.
Кроме того, вы можете абстрагироваться от некоторых функций из всех репозиториев ... и, поскольку вы используете Linq-to-Sql, вы, вероятно, можете ...
Вы можете реализовать базовый репозиторий, который в Общий способ реализует всю эту общую функциональность.
Следующий пример служит только для доказательства этого. Вероятно, это требует серьезного улучшения ...
interface IRepository<T> : IDisposable where T : class
{
IEnumerable<T> FindAll(Func<T, bool> predicate);
T FindByID(Func<T, bool> predicate);
void Insert(T e);
void Update(T e);
void Delete(T e);
}
class MyRepository<T> : IRepository<T> where T : class
{
public DataContext Context { get; set; }
public MyRepository(DataContext context)
{
Context = Context;
}
public IEnumerable<T> FindAll(Func<T,bool> predicate)
{
return Context.GetTable<T>().Where(predicate);
}
public T FindByID(Func<T,bool> predicate)
{
return Context.GetTable<T>().SingleOrDefault(predicate);
}
public void Insert(T e)
{
Context.GetTable<T>().InsertOnSubmit(e);
}
public void Update(T e)
{
throw new NotImplementedException();
}
public void Delete(T e)
{
Context.GetTable<T>().DeleteOnSubmit(e);
}
public void Dispose()
{
Context.Dispose();
}
}
Для меня шаблон репозитория заключается в том, чтобы обернуть тонкую оболочку вокруг вашей методологии доступа к данным. LINQ to SQL в вашем случае, но NHibernate, скрученный вручную в других. Я обнаружил, что делаю создание репозитория для каждой таблицы, поскольку это очень просто (как списки bruno, которые у вас уже есть). Он отвечает за обнаружение вещей и выполнение операций CRUD.
Но, как упоминает Йоханнес, у меня есть уровень обслуживания, который больше касается агрегированных корней. У меня был бы UserService с таким методом, как GetExistingUser (int id). Это вызовет внутренний метод UserRepository.GetById () для извлечения пользователя. Если ваш бизнес-процесс требует, чтобы класс пользователя, возвращаемый GetExistingUser (), почти всегда нуждался в заполнении свойства User.IsInRoles (), тогда просто сделайте так, чтобы UserService зависела от обоих UserRepository и RoleRepository. В псевдокоде это может выглядеть примерно так:
public class UserService
{
public UserService(IUserRepository userRep, IRoleRepository roleRep) {...}
public User GetById(int id)
{
User user = _userService.GetById(id);
user.Roles = _roleService.FindByUser(id);
return user;
}
UserRep и roleRep будут построены с вашими битами LINQ to SQL примерно так:
public class UserRep : IUserRepository
{
public UserRep(string connectionStringName)
{
// user the conn when building your datacontext
}
public User GetById(int id)
{
var context = new DataContext(_conString);
// obviously typing this freeform but you get the idea...
var user = // linq stuff
return user;
}
public IQueryable<User> FindAll()
{
var context = // ... same pattern, delayed execution
}
}
Лично я бы сделал классы репозитория внутренне ограниченными и имел UserService и другие классы XXXXXService public, поэтому держите своих потребителей API службы честными. Итак, я снова считаю, что репозитории более тесно связаны с взаимодействием с хранилищем данных, но уровень вашего сервиса более тесно связан с потребностями вашего бизнес-процесса.
Я часто действительно переоценивал гибкость Linq для Объекты и все такое, а также использование IQuerable и др. вместо того, чтобы просто создавать методы обслуживания, которые выдают то, что мне действительно нужно. Пользовательский LINQ там, где это необходимо, но не пытайтесь заставить репозиторий делать все.
public IList<User> ActiveUsersInRole(Role role)
{
var users = _userRep.FindAll(); // IQueryable<User>() - delayed execution;
var activeUsersInRole = from users u where u.IsActive = true && u.Role.Contains(role);
// I can't remember any linq and i'm type pseudocode, but
// again the point is that the service is presenting a simple
// interface and delegating responsibility to
// the repository with it's simple methods.
return activeUsersInRole;
}
Итак, это было немного бессвязно. Не уверен, что я действительно помог кому-то, но я советую не слишком увлекаться методами расширения и просто добавьте еще один слой, чтобы каждая из движущихся частей была довольно простой. У меня работает.
Если мы напишем наш уровень репозитория так подробно, как предлагает Womp, что мы будем помещать в наш уровень обслуживания. Должны ли мы повторять вызовы одного и того же метода, которые в основном будут состоять из вызовов соответствующего метода репозитория, для использования в наших контроллерах или выделенном коде? Это предполагает, что у вас есть уровень обслуживания, на котором вы пишете свою проверку, кеширование, рабочий процесс, код аутентификации / авторизации, верно? Или я далеко от базы?