Сервис и Репозиторий -> Помогите мне не повторять код

Я создаю веб-приложение MVC на C#, оно началось с простого и понятного DbContext. Затем я создал репозитории, чтобы я мог писать модульные тесты... Затем я реализовал внедрение зависимостей... о нет, теперь я хочу создать сервисный уровень между моим контроллером и репозиторием.

Там почти все, кроме того, что я не знаю, как вызывать универсальные функции из моего хранилища в сервисе.

Должен ли я повторить все общие функции репозитория в сервисе?

Вот общий репозиторий:

public interface IRepository<TEntity> : IDisposable where TEntity : class
{

    int Count { get; }

    IEnumerable<TEntity> Get(
      Expression<Func<TEntity, bool>> filter = null,
      Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
      string includeProperties = "");

    IQueryable<TEntity> All();

    TEntity GetByID(object id);
    void Insert(TEntity entity);
    void Delete(object id);
    void Delete(TEntity entityToDelete);
    void Update(TEntity entityToUpdate);
    void Save();

}

EF Repository:

public abstract class Repository<CEntity, TEntity> : IRepository<TEntity> where TEntity : class 
                                                                    where CEntity : DbContext, new()
{
    private CEntity entities = new CEntity();
    protected CEntity context
    {
        get { return entities; }
        set { entities = value; }
    }

    public virtual int Count 
    {
        get { return entities.Set<TEntity>().Count(); }
    }

    public virtual IEnumerable<TEntity> Get(
        Expression<Func<TEntity, bool>> filter = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        string includeProperties = "")
    {
        IQueryable<TEntity> query = entities.Set<TEntity>();

        if (filter != null)
        {
            query = query.Where(filter);
        }

        foreach (var includeProperty in includeProperties.Split
            (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }

        if (orderBy != null)
        {
            return orderBy(query).ToList();
        }
        else
        {
            return query.ToList();
        }
    }

    public virtual IQueryable<TEntity> All()
    {
        return entities.Set<TEntity>().AsQueryable();
    }

    public virtual TEntity GetByID(object id)
    {
        return entities.Set<TEntity>().Find(id);
    }

    public virtual void Insert(TEntity entity)
    {
        entities.Set<TEntity>().Add(entity);
    }

    public virtual void Delete(object id)
    {
        TEntity entityToDelete = entities.Set<TEntity>().Find(id);
        Delete(entityToDelete);
    }

    public virtual void Delete(TEntity entityToDelete)
    {
        if (context.Entry(entityToDelete).State == EntityState.Detached)
        {
            entities.Set<TEntity>().Attach(entityToDelete);
        }
        entities.Set<TEntity>().Remove(entityToDelete);
    }

    public virtual void Update(TEntity entityToUpdate)
    {
        entities.Set<TEntity>().Attach(entityToUpdate);
        context.Entry(entityToUpdate).State = EntityState.Modified;
    }

    public virtual void Save()
    {
        entities.SaveChanges();
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                context.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Сервис:

public class CampaignService : ICampaignService 
{
    private readonly IRepository<Campaign> _campaignRepository;

    public CampaignService(IRepository<Campaign> campaignRepository)
    {
        _campaignRepository = campaignRepository;
    }

    public Campaign GetLatestCampaign()
    {
        var query = _campaignRepository.Get(x => x.CreatedOn != null, q => q.OrderByDescending(s => s.CreatedOn));
        Campaign result = query.First();

        return result;
    }

}

public interface ICampaignService
{
    Campaign GetLatestCampaign();
}

Но, очевидно, я не могу получить общие свойства в контроллере:

IntelliSense

Должен ли я повторить все функции репозитория в сервисе? Но вместо получения из DbContext он получает его из репозитория..

Кажется, много повторяющегося кода, не правда ли?

Или вы должны повторить код, но не делать сервис общим и конкретно указывать, что сервис делает, т.е. _campaignService.AddCampaign(c); вместо _campaignService.Add(c);

2 ответа

Это антикоррупционный слой, так что да, вам придется пересмотреть контракт.

Думайте об этом так:

Общий интерфейс репозитория имеет задание: скрыть все детали реализации о том, как объекты сохраняются и извлекаются.

У интерфейса сервиса есть задание: представлять варианты использования.

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

Еще один способ думать об этом: случайно, если ваш интерфейс службы выглядит точно так же, как интерфейс вашего хранилища. Вы должны попытаться изменить свое мышление на язык, похожий на постоянство (Insert) или сервис-подобный язык (AddUser) в зависимости от того, над каким кодом вы работаете.

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

Каждый репозиторий выглядит так с самого начала и расширяется по мере необходимости. примечание: ModelBase - это простая базовая модель, которую мы используем во всех наших моделях. содержит такие вещи, как ID , LastUpdated , isDeleted , так далее

public abstract class RepositoryBase<TModel> where TModel : ModelBase, new()
{
    protected RepositoryBase(UserModel loggedOnUser,
                             IDbProvider dbProvider)
    {
        DbProvider = dbProvider;
        LoggedOnUser = loggedOnUser;
    }

    public virtual Guid Create(TModel model)
    {
        // Create the record
        DbProvider.Create(model);
        return model.Id;
    }

   public virtual TModel GetById(Guid id)
    {
        var model = DbProvider.Query<TModel>(m => m.Id == id).FirstOrDefault();

        if (model == null)
        {
            throw new NotFoundException(string.Format(NotFoundMessage, id));
        }

        return model;
    }

    public virtual IList<TModel> Find()
    {
        return DbProvider.Query<TModel>(m => m.IsDeleted == false).ToList();
    }

    public virtual void Update(TModel model)
    {
        // Set the update/create info
        SetCreateInfo(model);

        // Update the record
        try
        {
            DbProvider.Update(model);
        }
        catch (Exception ex)
        {
            ThrowKnownExceptions(ex);
        }
    }

    public virtual void Delete(TModel model)
    {
        // Do NOT SetUpdateInfo(model); it's being done in the Update method.
        model.IsDeleted = true;
        Update(model);
    }

    public virtual void Delete(Guid id)
    {
        var model = GetById(id);
        Delete(model);
    }
}

Тогда у нас есть общий сервисный слой

public abstract class ServiceBase<TModel, TViewModel> 
    where TModel : ModelBase, new()
    where TViewModel : ViewModelBase, new()
{
    private readonly IRepository<TModel, Guid> _repository;
    protected AutoMapper<TModel> ToModel;
    protected AutoMapper<TViewModel> ToViewModel;

    protected ServiceBase(IRepository<TModel, Guid> repository)
    {
        _repository = repository;
        ToModel = new AutoMapper<TModel>();
        ToViewModel = new AutoMapper<TViewModel>();
    }

    public virtual TViewModel Save(TViewModel viewModel)
    {
        if (viewModel.Id != Guid.Empty)
        {
            // The ModelObject Id is not empty, we're either updating an existing ModelObject
            // or we're inserting a new ModelObject via sync
            var model = _repository.GetById(viewModel.Id);
            if (model != null)
            {
                // Looks like we're updating a ModelObject because it's already in the database.
                _repository.Update(ToModel.BuildFrom(viewModel));
                return ToViewModel.BuildFrom(_repository.GetById(viewModel.Id));

            }
        }

        // The ModelObject is being created, either via a Sync (Guid Exists), or via an Insert (Guid doesn't Exist)
        var id = _repository.Create(ToModel.BuildFrom(viewModel));
        return ToViewModel.BuildFrom(_repository.GetById(id));
    }

    public virtual TViewModel GetById(Guid id)
    {
        var model = _repository.GetById(id);
        return ToViewModel.BuildFrom(model);
    }

    public virtual IList<TViewModel> Find()
    {
        return ToViewModel.BuildListFrom(_repository.Find());
    }

    public virtual void Delete(TViewModel viewModel)
    {
        var model = ToModel.BuildFrom(viewModel);
        _repository.Delete(model);
    }
}

Вот и все для общих вещей...##

каждый репозиторий и сервис будут зависеть от вышеизложенного.

любой репозиторий, который просто делает основной CRUD, будет выглядеть так

public class TenantRepository : RepositoryBase<TenantModel>, ITenantRepository
{
    public TenantRepository(UserModel loggedOnUser, IDbProvider dbProvider) : base(loggedOnUser, dbProvider)
    {
    }
}

И если нам когда-нибудь понадобится дополнительный метод, мы просто добавим его в интерфейс и конкретную реализацию. Если нам не нужно ничего, кроме базового CRUD, указанный выше репозиторий является "полным".

После всего этого у нас есть аналогичный общий сервисный уровень.

и наши конкретные сервисы так же просты в реализации, как и конкретные репозитории.

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

public class TenantService : ServiceBase<TenantModel, TenantViewModel>, ITenantService
{
    private readonly ITenantRepository _TenantRepository;

    public TenantService(ITenantRepository TenantRepository)
        : base(TenantRepository)
    {
        _TenantRepository = TenantRepository;
    }
}

и, наконец, некоторый код psuedo, чтобы показать, как мы "находим" через сервис.

var tenantRepository = new TenantRepository(myself, mydbProvider);
var tenantService = new TenantService(tenantRepository);
var tenants = tenantService.Find();

Вот и все. После того, как вы подключите свой BaseRepository и BaseService, расширение других для базового CRUD не потребует избыточного кода.

Другие вопросы по тегам