apiquestion
@apiquestion
Студент

Уровень доступа к данным .NET Core и Dapper?

Разрабатываю 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 мне не нужен).

Сейчас в моей голове все окончательно поломалось. Я не могу придумать как сделать проект и меня это дико раздражает. Не могу придумать хороший архитектурный подход.

Как вы реализовываете подобные проекты? Какие подходы используете?
  • Вопрос задан
  • 1320 просмотров
Пригласить эксперта
Ответы на вопрос 1
0X12eb
@0X12eb
Выбор Dapper ORM чем то аргументирован?
Если Вам действительно нужна та скорость с которой даппер маппит, но Вы готовы пожертвовать скоростью написания кода тогда делайте так:

1. Реализуем SqlBuilder под все нужды:
public interface ISqlBuilder
    {
        SqlBuilder.Template AddTemplate(string sql, dynamic parameters = null);
        ISqlBuilder LeftJoin(string sql, dynamic parameters = null);
        ISqlBuilder PagingLeftJoin(string sql, dynamic parameters = null);
        ISqlBuilder Where(string sql, dynamic parameters = null);
        ISqlBuilder PagingWhere(string sql, dynamic parameters = null);
        ISqlBuilder OrderBy(string sql, dynamic parameters = null);
        ISqlBuilder PagingOrderBy(string sql);
        ISqlBuilder Select(string sql, dynamic parameters = null);
        ISqlBuilder AddParameters(dynamic parameters);
        ISqlBuilder Join(string sql, dynamic parameters = null);
        ISqlBuilder GroupBy(string sql, dynamic parameters = null);
        ISqlBuilder Having(string sql, dynamic parameters = null);
    }


2. Под каждое конструирование запросов пишем свой класс, реализуем интерфейс IQueryBuilder для дальнейшей инъекции зависимости в сервисах:

using Dapper;

    public class MyEntityQueryBuilder : IQueryBuilder<T>
        where T: IEntity
    {
        private readonly ISqlBuilder _sqlBuilder;
        private readonly SqlBuilder.Template _entityTemplate;
        private readonly string entity_template = @"
               select 
                     /**select**/ 
               from 
                     entity_table e 
               /**leftjoin**/
               /**where**/ 
               /**orderby**/
        ";
        private readonly string split_on = "Id";

        public MyEntityQueryBuilder(ISqlBuilder sqlBuilder)
        {
            _sqlBuilder = sqlBuilder.AddTemplate(entity_template);
        }

        public Query Build(EntityFilter filter)
        {
            Select();
            LeftJoin();
            Where(filter);
            Order();
            return new Query
            {
                Sql = _entityTemplate.RawSql,
                Parameters = _entityTemplate.Parameters,
                SplitOn = split_on,
            };
        }

        private void Select()
        {
            _builder.Select("e.Id");
            _builder.Select("e.Title");
        }

        private void LeftJoin()
        {
            _builder.LeftJoin("asn_Responsibles r1 on r1.Id = e.MainResponsibleId");
            _builder.LeftJoin("asn_EventResponsibles er on er.EventId = e.Id");
        }

        private void Where(EntityFilter filter)
        {
            _builder.Where("e.IsDeleted = 0");
            if (filter == null)
            {
                return;
            }
            if (filter.Id.HasValue)
            {
                _builder.Where("e.Id = @id", new { id = filter.Id });
            }
            if (filter.IsRootOnly)
            {
                _builder.Where("e.ParentId is null");
            }
        }

        private void Order()
        {
            _builder.OrderBy("e.CreateDate");
        }
    }


3. Нужен абстрактный репозиторий? Реализуйте IRepository и инжекте уже туда Ваши свежеиспеченные QueryBuilders для дальнейшего маппинга даппером.

Удачи (:
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы