Шаблон создания репозитория /службы. Правильный способ повесить manBrainfuck на компилятор сборки x86.

Я использую шаблон дизайна репозитория /службы в своих проектах, и я нашел что-то, что может быть немного избыточным. Я пишу лишний код?

Имея это в виду, вот моя структура:

public class Competition
{
    public int Id { get; set; }
    [Required] public string UserId { get; set; }
    public string UserName { get; set; }
    public DateTime DateCreated { get; set; }
    public DateTime? DateModified { get; set; }
    [Required] public string Name { get; set; }
}

Это пример класса POCO, но, как вы увидите, я использую Generics, поэтому он может быть любым:

internal interface IRepository<TEntity> : IDisposable where TEntity : class
{
    IList<TEntity> GetAll();
    TEntity Get(int id);
    void Save(TEntity model);
    void Delete(int id);
}

Мой interface реализует IDisposable и позволяет установить любой объект класса как TEntity:

internal class CompetitionRepository : IRepository<Competition>
{
    private readonly string userId;
    private readonly CompetitionProvider provider;

    public CompetitionRepository(string userId) : this(userId, "DefaultConnection")
    {
    }

    public CompetitionRepository(string userId, string connectionString)
    {
        this.userId = userId;
        this.provider = new CompetitionProvider(connectionString);
    }

    public IList<Competition> GetAll()
    {
        return provider.Get(this.userId);
    }

    public Competition Get(int id)
    {
        return GetAll().Where(model => model.Id == id).SingleOrDefault();
    }

    public void Save(Competition model)
    {
        provider.Save(model);
    }

    public void Delete(int id)
    {
        provider.Delete(id);
    }

    public void Dispose()
    {
        provider.Dispose();
    }
}

Мой репозиторий, который реализует IRepository, указывая Competition как TEntity. Этот репозиторий запрашивает мою базу данных, следовательно, провайдер.

В этом репозитории я выполняю минимальное количество запросов для моей базы данных:

public class CompetitionService : IRepository<Competition>
{

    private readonly string userId;
    private readonly IRepository<Competition> repository;

    public CompetitionService(string userId) : this(userId, "DefaultConnection")
    {
    }

    public CompetitionService(string userId, string connectionString)
    {
        this.userId = userId;
        this.repository = new CompetitionRepository(this.userId, connectionString);
    }

    public IList<Competition> GetAll()
    {
        return this.repository.GetAll();
    }

    public Competition Get(int id)
    {
        return this.repository.Get(id);
    }

    public void Save(Competition model)
    {
        if (model.Id > 0)
        {
            model.UserId = userId;
            model.DateCreated = DateTime.UtcNow;
        } else
        {
            model.DateModified = DateTime.UtcNow;
        }

        this.repository.Save(model);
    }

    public void Delete(int id)
    {
        this.repository.Delete(id);
    }

    public void Dispose()
    {
        this.repository.Dispose();
    }
}

Теперь это мое служение. Например, вы можете увидеть, что метод Save присваивает значения в зависимости от того, создаем ли мы или обновляем. Класс Service может иметь что угодно, например, выполнять загрузку или что-то еще не связанное с источником данных.

Репозиторий Repository предназначен для взаимодействия только с источником данных, и никакой другой код отсутствует. Это позволяет заменять репозиторий другим, запрашивающим другой источник данных.

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

GetAll()

Get()

Save()

Delete()

Я реализовал IRepository<Competition>. У службы могут быть другие общедоступные методы, но это не имеет значения, потому что в моих контроллерах я бы назвал это следующим образом:

//
// POST: /Competitions/Create
[HttpPost]
public ActionResult Create(Competition model)
{
    try
    {
        using (var service = new CompetitionService(User.Identity.GetUserId()))
        {
            service.Save(model);
        }

        return RedirectToAction("Index");
    }
    catch
    {
        return View(model);
    }
}
  1. Считаете ли вы, что это хорошая практика или вы думаете, что я слишком сильно ее убил?
  2. Как вы думаете, я должен внедрить IRepository в службе?
  3. В более экстремальном случае, как вы думаете, я должен даже беспокоиться осервис?

(NB: я не хочу удалять службу, потому что иногда есть много кода, который должен туда попасть, что я не хочу забивать мои контроллеры)

0 голосов | спросил N3buchadnezzarskiwi 7 J0000006Europe/Moscow 2016, 14:26:38

3 ответа


22

Вы на правильном пути. Разделение бизнес-логики (услуг) и логики доступа к данным (репозитории) - это хорошо, я настоятельно рекомендую это. Я создал общий репозиторий, реализующий интерфейс IRepository<TEntity>. Это немного отличается от вашего, но вы получаете точку (у меня есть ограничение BaseEntity, вы также можете использовать class):

public class EfRepository<TEntity> : IRepository<TEntity>
    where TEntity : BaseEntity, new()
{
    private readonly IDbContext _context;
    private IDbSet<TEntity> _entities;

    public EfRepository(IDbContext context)
    {
        _context = context;
    }

    private IDbSet<TEntity> Entities
    {
        get { return _entities ?? (_entities = _context.Set<TEntity>()); }
    }

    public TEntity GetById(object id)
    {
        return Entities.Find(id);
    }

    public void Insert(TEntity entity)
    {
        Entities.Add(entity);
        _context.SaveChanges();
    }

    public void Update(TEntity entity)
    {
        _context.SaveChanges();
    }

    public void Delete(int id)
    {
        // Create a new instance of an entity (BaseEntity) and add the id.
        var entity = new TEntity
                     {
                         ID = id
                     };

        // Attach the entity to the context and call the delete method.
        Entities.Attach(entity);
        Delete(entity);
    }

    public void Delete(TEntity entity)
    {
        Entities.Remove(entity);
        _context.SaveChanges();
    }

    public IList<TEntity> Table
    {
        get { return Entities.ToList(); }
    }
}

Это значительно уменьшает количество дубликатов кода, когда вы будете использовать определенные репозитории. Я использую это в своих сервисах, например:

public class ProductService : IProductService
{
    private readonly IRepository<Product> _productRepository;

    public ProductService(IRepository<Product> productRepository)
    {
        _productRepository = productRepository;
    }

    public bool AddProduct(Product product)
    {
        // Validate etc.
        _productRepository.Insert(product);

        // return validation result.
        return true;
    }
}

Как вы, возможно, заметили, я использую среду IoC для ввода зависимостей.

ответил Henk Mollema 10 +04002013-10-10T16:50:29+04:00312013bEurope/MoscowThu, 10 Oct 2013 16:50:29 +0400 2013, 16:50:29
13

Мне иногда кажется, что я бог, но моя реальная ситуация не может быть дальше.

Однако я готов поделиться своими мыслями о вашей ситуации.

Прежде всего, я бы никогда не избавился от вашего уровня обслуживания, я полностью согласен с тем, чтобы ваши контроллеры были как можно более легкими!

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

Вы также можете использовать ваш Provider непосредственно из своей службы и относиться к этому провайдеру как к репозиторию.

Что касается IRepository на уровне сервиса, я должен сказать, что я против. Если вы настроите свою службу в качестве репозитория, ваш контроллер будет ограничен этими CRUD-подобными методами. Это может сработать на раннем этапе, если у вас просто есть экраны, подобные CRUD, но я не очень люблю смотреть на службы таким образом.

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

Save и Edit может быть частью этого, но это будет AddNewAnswerToQuestion, RaiseFlagToQuestion, AddTagToQuestion (например). Я боюсь, что если вы ограничитесь CRUD-подобными методами, вам нужно будет сделать много структур AddTagToQuestion в вашем методе if (если тег был добавлен, если флаг был установлен), или ваш контроллер будет работать все больше и больше (вызовите метод Save, затем вызовите метод Save), потому что письмо нужно отправить в случае дополнительного тег).

Если вы делаете это немного более экстремально, вы больше не выполняете службу для каждого объекта , а для каждого действия .

В этот момент вы перестаете называть их services и начинаете называть их командами и используйте CQS (Разделение команд-запросов) , но этот выбор зависит от вас:)

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

ответил Kristof 10 +04002013-10-10T16:57:26+04:00312013bEurope/MoscowThu, 10 Oct 2013 16:57:26 +0400 2013, 16:57:26
11

Прежде всего: . Почему бы вам не реализовать свое репо как общее? Я имею в виду реализацию универсального интерфейса. В вашем решении вам нужно создать реализацию IRepository<Entity> для каждого нового POCO. Это раздует ваш код, и вы не будете использовать общий интерфейс.

Вот пример общей реализации репо:

public class Repository<T> : IRepository<T> where T : class, new()
{
    private IYourContext _context;

    public Repository(IYourContext context)
    {
        this._context = context;
    }

    public IQueryable<T> All
    {
        get { return _context.Set<T>(); }
    }

    public T Find(int id)
    {
        return _context.Set<T>().Find(id);
    }

    public void Delete(T entity)
    {
        if (_context.Entry(entity).State == EntityState.Detached)
            _context.Set<T>().Attach(entity);
        _context.Set<T>().Remove(entity);
    }

    public void Insert(T entity)
    {
        _context.Set<T>().Add(entity);
    }

    public void Update(T entity)
    {
        _context.Set<T>().Attach(entity);
        _context.Entry(entity).State = EntityState.Modified;
    }
}

В вашем интерфейсе и реализации могут быть разные методы. Если это не так, тогда вы можете использовать свое репо, например:

IRepository<YourPOCO> repo = new Repository<YourPOCO>();

Второй: Существует шаблон Unit Of Work, который часто реализуется с шаблоном Generic Repository. Это оболочка вокруг репозиториев, которая предоставляет способ обмена одним контекстом между всеми из них.

Вот пример:

public interface IUnitOfWork : IDisposable
{
    IRepository<Product> ProductRepository { get; }
    // other repositories                

    void SaveChanges();
    void RollbackChanges();
}

И реализация:

public class UnitOfWork : IUnitOfWork
{
    private IRepository<Product> _productRepository;
    private IYourContext _context;
    private bool _disposed = false;

    public UnitOfWork(IYourContext context)
    {
        this._context = context;
    }

    // Lazy Loading pattern is also often used here
    public IRepository<Product> ProductRepository
    {
        get
        {
            if (this._productRepository == null)
                this._productRepository = new Repository<Product>(_context);
            return _productRepository;
        }
    }

    public void SaveChanges()
    {
        _context.SaveChanges();
    }

    // implement other functionality
}

Когда вы делаете изменения в разных репозиториях, вам нужно просто вызвать метод SaveChanges() класса UnitOfWork только один раз. Таким образом, это будет одна транзакция.

Кроме того, возможно, вы заметили, что я использую интерфейсы всюду, даже в реализации контекста db. Это поможет вам в случае необходимости протестировать вашу логику в будущем. И используйте Dependency Injection!

В контексте:

public interface IYourContext : IDisposable
{
    DbSet<T> Set<T>() where T : class;
    DbEntityEntry Entry(object entry);
    int SaveChanges();
}

public class YourContext : DbContext, IYourContext
{
    public NorthwindContext(IConnectionFactory connectionFactory) : base(connectionFactory.ConnectionString) { }

    public DbSet<Product> Products { get; set; }
    // other DbSets ..
}

И фабрика соединений (без нее вы получите исключение в контейнере DI):

public interface IConnectionFactory
{
    string ConnectionString { get; }
}

public class ConnectionFactory : IConnectionFactory
{
    public string ConnectionString
    {
        get { return "YourConStringName"; }
    }
}

Подводя итог: Реализовать общий репозиторий и единицу рабочих шаблонов как DAL (уровень доступа к данным), а затем вы можете создать все уровни обслуживания с помощью BL (Business Logic) поверх них.

ответил Aleksei Chepovoi 10 +04002013-10-10T16:53:43+04:00312013bEurope/MoscowThu, 10 Oct 2013 16:53:43 +0400 2013, 16:53:43

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

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132