繁体   English   中英

如何在 C# 中正确抽象数据访问层(用于数据库、rest api 调用、json)?

[英]How to correctly abstract data access layer (for database, rest api calls, json) in C#?

我一直在开发一个应用程序,它使用 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 中。 我需要让它依赖于特定的连接,否则我无法编译它。 我想要的是确保我可以通过更改注入的参数来更改连接,并且一切都会正常工作。

您能否就如何确保我可以轻松更改连接以及这种架构是否很好提出建议?

我想你可能把这个问题复杂化了。 在我看来,您有两个数据源来检索相同的数据——一个是 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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM