Вопрос по linq-to-sql, c#, domain-driven-design, repository, design-patterns – Загрузка подзаписей в шаблоне репозитория

12

Использование LINQ TO SQL в качестве основы решения на основе репозитория. Моя реализация выглядит следующим образом:

IRepository

FindAll
FindByID
Insert
Update
Delete

Затем у меня есть методы расширения, которые используются для запроса результатов как таковые:

WhereSomethingEqualsTrue() ...

Мой вопрос заключается в следующем:

Хранилище «Мои пользователи» имеет N ролей. Создать ли репозиторий ролей для управления ролями? Я волнуюсь, что в итоге я создам десятки репозиториев (по 1 на таблицу почти за исключением таблиц объединения), если я пойду по этому пути. Распространен ли репозиторий на таблицу?

Ваш Ответ

4   ответа
1

Если мы напишем наш уровень хранилища так подробно, как предполагает Womp, что мы добавим в наш уровень обслуживания. Должны ли мы повторять одни и те же вызовы методов, которые в основном состоят из вызовов соответствующего метода репозитория, для использования в наших контроллерах или в коде? Это предполагает, что у вас есть сервисный уровень, где вы пишете свой валидацию, кэширование, рабочий процесс, код аутентификации / авторизации, верно? Или я далеко от базы?

32

Если вы строите свой репозиторий так, чтобы он был специфичен для одной сущности (таблицы), так чтобы каждая сущность имела список методов в интерфейсе IRepository, который вы перечислили выше, то то, что вы действительно делаете, это реализацияАктивная запись шаблон.

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

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

Ваши операции с доменом хранилища должны выглядеть примерно так:

userRepository.FindRolesByUserId(int userID)
userRepository.AddUserToRole(int userID)
userRepository.FindAllUsers()
userRepository.FindAllRoles()
userRepository.GetUserSettings(int userID)

так далее...

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

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

Отличный пост. Keith Adler
Ну вот и все. Узнай что-нибудь каждый день. Мне нужно изменить свой подход :-)
Ага. Я тоже кое-что узнаю с этим ответом. Я знал, что было бы логично иметь несколько репозиториев, но не знал, что каждый репозиторий должен быть построен вокруг агрегатов. Имеет полный смысл.
+1 отличный ответ DDD
Как насчет того, чтобы иметь отдельный репозиторий для каждой части агрегирования, а затем иметь другой репозиторий агрегирования, который объединяет базовые репозитории. Таким образом, вы можете использовать один и тот же репозиторий во многих репозиториях? Это плохая практика дизайна?
1

Для меня шаблон репозитория - это создание тонкой оболочки вокруг вашей методологии доступа к данным. LINQ to SQL в твоем случае, но NHibernate, свернутый вручную в других. Я обнаружил, что занимаюсь созданием репозитория для каждой таблицы, что очень просто (например, списки bruno у вас уже есть). Это ответственно за поиск вещей и выполнение операций CRUD.

Но тогда у меня есть уровень обслуживания, который больше связан с совокупными корнями, как упоминает Йоханнес. Я хотел бы иметь UserService с методом, подобным GetExistingUser (int id). Это вызвало бы внутренний метод UserRepository.GetById () для получения пользователя. Если ваш бизнес-процесс требует, чтобы пользовательский класс, возвращаемый GetExistingUser (), почти всегда нуждался в заполнении свойства User.IsInRoles (), тогда просто нужно, чтобы UserService зависел от обоих UserRepositoryand 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, так что держите своих потребителей API сервиса честными. Итак, я снова считаю, что репозитории более тесно связаны с процессом общения с хранилищем данных, но уровень обслуживания более тесно связан с потребностями вашего бизнес-процесса.

Я часто обнаруживал, что действительно задумываюсь над гибкостью Linq to Objects и всего такого, и использую IQuerableet al вместо того, чтобы просто создавать методы обслуживания, которые выплевывают то, что мне действительно нужно. Пользователь 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;
}

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

4

Is a Repository per Table common?

Нет, но у вас все еще может быть несколько хранилищ. Вы должны построить хранилище вокруг агрегата.

Кроме того, вы можете абстрагировать некоторые функции от всех репозиториев ... и, поскольку вы используете 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();
        }
    }
Подскажите, пожалуйста, как клиентский класс использует функцию "T FindByID (Func & lt; T, bool & gt; предикат)". Я ищу код демонстрации.
@ Йоханнес Да, но как? Keith Adler
Нет, каждый репозиторий должен инкапсулировать логику персистентности для одного агрегатного корня

Похожие вопросы