Разрабатываю REST API с использованием ASP.NET Core. Данные от API в основном будут получать, запись будет происходить намного реже.
Получение данных должно быть достаточно функциональным: должна быть возможность фильтровать, сортировать данные множеству полей, получать только необходимые поля и т.д. В качестве ORM я использую Dapper.
Раньше я использовал паттерн репозиторий и Entity Framework.
Выглядел мой код приблизительно вот так:
public class BaseRepository : IDisposable
{
public string ConnectionString { get; set; }
protected BaseRepository()
{
}
protected BaseRepository(string connectionString)
{
ConnectionString = connectionString;
}
protected string GetConnectionStringValue(string connectionStringName) { ... }
}
public abstract class EntityBaseRepository : BaseRepository
{
private DbContext _context;
protected EntityBaseRepository(): base("connectionString")
{
}
protected DbContext Context => _context ?? (_context = CreateEntityContext(GetConnectionStringValue(ConnectionString)));
protected abstract DbContext CreateEntityContext(string connectionString);
protected virtual void SaveEntity<T>(T entity) where T : Entity { ... }
protected virtual void DeleteEntity<T>(T entity) where T : Entity{ ... }
}
public abstract class EntityBaseRepository<TEntity, TSearchOptions, TLoadOptions> : EntityBaseRepository
where TEntity : Entity
where TSearchOptions : SearchOptions
where TLoadOptions : LoadOptions
{
protected IEnumerable<TEntity> GetEntities(TSearchOptions searchOptions, TLoadOptions loadOptions) { ... }
protected async Task<IEnumerable<TEntity>> GetEntitiesAsync(TSearchOptions searchOptions, TLoadOptions loadOptions) { ... }
protected int GetCount(TSearchOptions searchOptions) { ... }
protected TEntity GetEntity(long id, TLoadOptions loadOptions) { ... }
protected virtual IQueryable<TEntity> ApplySearchOptions(DbContext context, IQueryable<TEntity> entities, TSearchOptions searchOptions) { ... }
protected virtual IQueryable<TEntity> ApplyLoadOptions(DbContext context, IQueryable<TEntity> entities, TLoadOptions loadOptions) { ... }
}
В методе ApplyLoadOptions применялись опции загрузки (напр. количество получаемых записей, offset, сортировки и т.д).
ApplySearchOptions применял опции поиска.
Для каждой сущности я реализовывал отдельный репозиторий, который был унаследован от EntityBaseRepository, а так же специфичные для модели опции поиска и загрузки.
При использовании EF с этим подходом не возникало проблем. Но при переходе на Dapper я столкнулся с тем, что конструировать запросы с этим подходом достаточно проблематично. Для того, чтобы упростить эту задачу я написал конструктор SQL запросов (что-то вроде
https://laravel.com/docs/5.4/queries) и маппер для сущностей (что-то вроде EF Fluent API). Но у меня осталась еще одна проблема, решение для которой я пока не смог найти.
Базовый класс опций поиска выглядит следующим образом:
public class SearchOptions
{
public List<object> IDs { get; set; }
}
public class SearchOptions<TKey> : SearchOptions
{
public new List<TKey> IDs { get; set; }
}
С использованием написанных мною классов я применяю опции поиска вот так:
protected virtual Query ApplySearchOptions(Query query, TSearchOptions searchOptions)
{
if (searchOptions.IDs.Any())
query.WhereIn(FluentMapper.KeyColumnName<TEntity>(), searchOptions.IDs);
return query;
}
Проблема связана с alias. Если где-то мне нужно будет определить для таблицы Alias, то я выхвачу exception.
Я долго думал над тем, как же мне лучше реализовать проект. Вчера наткнулся на CQRS. Мне этот подход показался очень интересным. Но как лучше реализовать этот подход в моем проекте я не знаю (EventSource мне не нужен).
Сейчас в моей голове все окончательно поломалось. Я не могу придумать как сделать проект и меня это дико раздражает. Не могу придумать хороший архитектурный подход.
Как вы реализовываете подобные проекты? Какие подходы используете?