Как правильно абстрагировать слой доступа к данным (для базы данных, остальные вызовы API, JSON) в C #? - PullRequest
0 голосов
/ 27 декабря 2018

Я разрабатывал приложение, которое использует различные подключения к данным (например, базу данных, остальные вызовы API, файлы конфигурации JSON) в C #.В настоящее время я пытаюсь создать разумную абстракцию уровня доступа к данным, которая позволила бы легко переключаться между ними.Для каждого из них требуются разные параметры подключения, а также они работают по-разному.

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

    public interface IQuery<TResult>
    {
    }

    public interface IQueryHandler<TQuery, TResult>
        where TQuery : IQuery<TResult>
    {
        TResult Handle(TQuery query);
    }

    public class DatabaseQuery<TResult> : IQuery<IEnumerable<TResult>>
    {
        public string ConnectionString { get; set; }

        public string CommandText { get; set; }
    }

    public class DatabaseConnection<TQuery, TResult> : IQueryHandler<TQuery, IEnumerable<TResult>>
        where TQuery : DatabaseQuery<TResult>
    {
        public IEnumerable<TResult> Handle(TQuery query)
        {
            var results = new List<TResult>();

            using (var connection = new SqlConnection(query.ConnectionString))
            using (var command = new SqlCommand(query.CommandText, connection))
            {
                connection.Open();

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        results.Add(...
                    }
                }
            }

            return results;
        }
    }

    public class JsonQuery<TResult> : IQuery<IEnumerable<TResult>>
    {
        public string FileLocation { get; set; }

        public Func<TResult, bool> Condition { get; set; }
    }

    public class JsonConnection<TQuery, TResult> : IQueryHandler<TQuery, IEnumerable<TResult>>
        where TQuery : JsonQuery<TResult>
    {
        public IEnumerable<TResult> Handle(TQuery query)
        {
            var text = File.ReadAllText(query.FileLocation);
            return Deserialize<TResult>(text).Results.Where(query.Condition);
        }
    }

    public interface IQueryBuilder<TQuery, TParameters>
    {
        TQuery Build(TParameters parameters);
    }

    public class GetAccountsByStatusAndBalanceHigherThanQueryParameters
    {
        public string Status { get; set; }

        public decimal Balance { get; set; }
    }        

    public class GetAccountsByStatusAndBalanceHigherThan_DatabaseQueryBuilder : 
        IQueryBuilder<DatabaseQuery<Account>, GetAccountsByStatusAndBalanceHigherThanQueryParameters>
    {
        public DatabaseQuery<Account> Build(GetAccountsByStatusAndBalanceHigherThanQueryParameters parameters)
        {
            return new DatabaseQuery<Account>()
            {
                ConnectionString = "connString",
                CommandText = $"SELECT * FROM Accounts WHERE Status = {parameters.Status} AND Balance = {parameters.Balance}"
            };
        }
    }

    public class GetAccountsByStatusAndBalanceHigherThan_JsonQueryBuilder
        : IQueryBuilder<JsonQuery<Account>, GetAccountsByStatusAndBalanceHigherThanQueryParameters>
    {
        public JsonQuery<Account> Build(GetAccountsByStatusAndBalanceHigherThanQueryParameters parameters)
        {
            return new JsonQuery<Account>()
            {
                FileLocation = "fileLocation",
                Condition = acc => acc.Status == parameters.Status && acc.Balance > parameters.Balance
            };
        }
    }

    public class GetAccountsByStatusAndBalanceHigherThanQuery : IQuery<IEnumerable<Account>>
    {
        public string Status { get; set; }

        public decimal Balance { get; set; }
    }

    public class GetAccountsByStatusAndBalanceHigherThanQueryHandler :
        IQueryHandler<GetAccountsByStatusAndBalanceHigherThanQuery, IEnumerable<Account>>           
    {
        private readonly IQueryBuilder<JsonQuery<Account>, GetAccountsByStatusAndBalanceHigherThanQueryParameters> 
            _queryBuilder;

        private readonly IQueryHandler<JsonQuery<Account>, IEnumerable<Account>> _connection;

        public GetAccountsByStatusAndBalanceHigherThanQueryHandler(
            IQueryBuilder<JsonQuery<Account>, GetAccountsByStatusAndBalanceHigherThanQueryParameters> queryBuilder,
            IQueryHandler<JsonQuery<Account>, IEnumerable<Account>> connection)
        {
            _queryBuilder = queryBuilder;
            _connection = connection;
        }

        public IEnumerable<Account> Handle(GetAccountsByStatusAndBalanceHigherThanQuery query)
        {
            var jsonQuery = _queryBuilder.Build(new GetAccountsByStatusAndBalanceHigherThanQueryParameters
            { 
                Status = query.Status,
                Balance = query.Balance
            });

            return _connection.Handle(jsonQuery);
        }
    }

Итак, есть два соединения - одно соединение с базой данных и одно соединение с файлом Json.Я поместил настройки для соединений в запросы - и хотя для соединения с базой данных требуется строка соединения и команда SQL, для соединения Json требуется местоположение файла и некоторая фильтрация результатов.Проблема в последнем обработчике запросов - GetAccountsByStatusAndBalanceHigherThanQueryHandler.Мне нужно, чтобы это зависело от конкретного соединения, иначе я не смогу его скомпилировать.Я хочу убедиться, что я могу изменить соединение, просто изменив введенные параметры, и все будет работать правильно.

Не могли бы вы посоветовать, как сделать так, чтобы я мог легко изменить соединения, а такжеэта архитектура хороша вообще?

1 Ответ

0 голосов
/ 27 декабря 2018

Я думаю, что вы можете это усложнить.Мне кажется, что у вас есть два источника данных для извлечения одних и тех же данных - один является источником данных json и базой данных.Это вполне вписывается в «один интерфейс, несколько реализаций».Вам также понадобится класс, чтобы разобраться, какую реализацию вам следует использовать (это идеальный вариант использования для фабрики).Сложная логика запросов, которую вы пытаетесь передать конструкторам, вы можете передать в качестве аргументов метода.

Код будет выглядеть примерно так:

public interface IAccountDataAccess 
{
  IEnumerable<Account> GetAccountsHigherThanBalance(Status status, Balance balance);
  // etc.
}

public class JsonAccountDataAccess : IAccountDataAccess 
{

  private string jsonFilePath;
  public JsonAccountDataAccess(string _jsonFilePath)
  {
    jsonFilePath = _jsonFilePath;
  }

  public IEnumerable<Account> GetAccountsHigherThanBalance(Status status, Balance balance) 
  {
    // read json file, extract data, etc.
  }
}

public class DatabaseAccountDataAccess : IAccountDataAccess 
{

  private string connectionString;
  public DatabaseAccountDataAccess(string _connectionString)
  {
    connectionString = _connectionString;
  }

  public IEnumerable<Account> GetAccountsHigherThanBalance(Status status, Balance balance) 
  {
    // read database, extract data, etc.
  }
}

Вот сложная часть - вам нуженФабрика выплюнуть правильную реализацию, учитывая некоторые входные данные.Я не уверен, как вы планируете решить, использовать ли JSON или базу данных, но при условии, что ваш клиентский класс знает:

public class AccountDataAccessFactory 
{
  public IAccountDataAccess GetDataAccess(bool useDatabase) 
  {
    if(useDatabase) // you could use arbitrarily complex logic here
      return new DatabaseAccountDataAccess(connString);
    else 
      return new JsonAccountDataAccess(jsonFilePath);
  }
}

Ваш клиентский класс может использовать его следующим образом:

var factory = new AccountDataAccessFactory();
var dataAccess = factory.GetDataAccess(true);
var accounts = dataAccess.GetAccountsHigherThanBalance(status, balance);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...