Сервис и Репозиторий -> Помогите мне не повторять код
Я создаю веб-приложение 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();
}
Но, очевидно, я не могу получить общие свойства в контроллере:
Должен ли я повторить все функции репозитория в сервисе? Но вместо получения из 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 не потребует избыточного кода.