如何在C#中正确抽象数据访问层(用于数据库,REST API调用,JSON)?

时间:2018-12-26 22:07:23

标签: c# json dependency-injection cqrs data-access-layer

我一直在开发一个使用C#中的各种数据连接(例如数据库,rest api调用,json配置文件)的应用程序。我目前正在努力创建一个明智的数据访问层抽象,以便能够轻松地在这些之间进行切换。这些中的每一个都需要不同的连接设置,并且工作方式也不同。

我看了Repository模式的示例,但这并不完全适合我的需求。我希望能够定义一些查询模式,可以对其进行参数设置,并且该查询将能够处理参数。我目前拥有的示例:

    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 :(得分:0)

我认为您可能使这一过程复杂化了。在我看来,您有两个用于检索相同数据的数据源-一个是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);