【问题标题】:Making an application Database Agnostic使应用程序数据库不可知
【发布时间】:2017-08-11 13:42:58
【问题描述】:

我有一个现有的 C# 应用程序,我打算让数据库引擎完全从业务逻辑中抽象出来。

这是我的制作方法-

public abstract class DbEngine // I can also make it an Interface
{       
}

public class SQLDBEngine : DbEngine
{
    public bool ExecuteSP(string SPName)
    {           
    }

    public void ExecuteInlineQuery(String SQLQuery)
    {
    }
}

public class MySQLDBEngine : DbEngine
{
    public bool ExecuteSP(string SPName)
    {
    }

    public void ExecuteInlineQuery(String SQLQuery)
    {
    }
}

然后我有一个工厂类,它负责实例化适当的 DbEngine 对象 -

public class ConnectionManager
{
    string CurrentEngine;

    public ConnectionManager()
    {
        // Read from configuration file to know which database to configure
        // Config returns wither MS or MY
        // MS = SQLDBEngine
        // MY = MYSQLDBEngine       
    }

    public DbEngine GetDBInstance()
    {
        switch(CurrentEngine)
        {
            case "MS":
                                return new SQLDBEngine();

            case "MY":
                                return new MySQLDBEngine();

            default:
                                return new SQLDBEngine();               
        }
    }
}

业务逻辑将只与 ConnectionManager 对象交互,从而完全从中抽象出数据库。

客户端将与以下代码交互-

ConnectionManager conn  = new ConnectionManager();
DBEngine obj = conn.GetDBInstance();

obj.ExecuteInlineQuery("select * from tblItems");

这里的问题是,如果我们引入 MongoDBEngine 作为新的数据库引擎,这将再次需要 MongoDBEngine 类 - 但由于它没有类似存储过程的功能,所以 ExecuteSP 没有意义,因此业务逻辑调用将失败。

我只是试图从业务逻辑中封装数据库引擎,这样当数据库发生变化时,业务逻辑不应该发生任何变化。

有没有我可以遵循的设计模式或技术?

【问题讨论】:

  • 微软的实体框架就是这样做的一个尝试:它为所连接的任何数据库提供了一个抽象的(基于 LINQ 的)接口,并且它掩盖了其特定于实现的连接器中缺失的功能。例如,它可以将查询转换为 tsql 和 pl/SQL。祝你好运,如果你要自己实现它,我曾经为 mssql、pl/SQL、mysql 和 access 做过,把自己限制在四个数据库系统功能的一个公共子集是一个巨大的痛苦。无论如何,抽象然后使用 ExecuteInlineQuery 是毫无意义的 IMO。

标签: c# asp.net design-patterns


【解决方案1】:

您应该抽象出访问方法,而不是试图将数据库的功能公开给应用程序。与其提供执行查询的能力,不如将查询或存储过程调用或插入/更新/删除命令嵌入到您的存储库类中。

public class MySQLCustomerRepository : ICustomerRepository
{
    private readonly string _connectionString;

    public MySQLCustomerRepository(string connectionString)
    {
        _connectionString = connectionString;
    }

    public List<CustomerOrder> GetCustomerOrdersByDate(int customerId, DateTime startDate, DateTime endDate)
    {
        //open a MySQL connection and execute query to retrieve customer orders
    }
}

public class MongoCustomerRepository : ICustomerRepository
{
    private readonly string _connectionString;

    public MongoCustomerRepository(string connectionString)
    {
        _connectionString = connectionString;
    }

    public List<CustomerOrder> GetCustomerOrdersByDate(int customerId, DateTime startDate, DateTime endDate)
    {
        //connect to mongo, get customers by running query
    }
}

public interface ICustomerRepository
{
    List<CustomerOrder> GetCustomerOrdersByDate(int customerId, DateTime startDate, DateTime endDate);
}

消费应用程序本身不应该知道查询,因为存储库本身会处理这些细节。

其中的部分原因是每个数据库都有不同的查询和对其有意义的命令。为 MS SQL 编写的 SQL 查询可能与为 Oracle 编写的查询不同,当然 SQL 在非 SQL 数据库上根本无法工作。因此,每个实现都应该负责为其对应的数据库使用正确的交互,而您的消费应用程序不需要知道它是如何工作的,因为它应该始终通过接口访问数据库,而不是通过具体的实现。

【讨论】:

  • 我喜欢这个答案并觉得它很有用。特别是因为它专注于泄漏实现问题。很多这类答案直接跳到接口背后的抽象,但抽象基本上泄露了实现的所有内容,只是一个泄露的接口。
【解决方案2】:

我认为梅森的最后一个答案是通过良好的设计向前迈出的一步。我会在 OO 设计中更进一步,并尝试将其视为 Dto 的对象页面并返回一系列内容。我要做的一个改变是将方法的参数封装在对象内部并通过构造函数注入它们,因此这样您将有多个实现查询抽象的类(一个用于分页序列的类,另一个用于搜索 id 的类,开始和结束时间等。结果将是相同的返回类型)而不是一个大怪物,一堆方法返回相同的类型。这样你可以获得更好的可维护性,所以如果你对特定查询的 mongo 类有问题,你检查一个小类而不是 GOD 类,这里有一些分页设计:

/// <summary>
/// DTO
/// </summary>
public class CustomerOrder
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Value { get; set; }
}

/// <summary>
/// Define a contract that get a sequence of something
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IFetch<T>
{
    IEnumerable<T> Fetch();
}

/// <summary>
/// Define a pageTemplate
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class PageTemplate<T> : IFetch<T>
{
    protected readonly int pageSize;
    protected readonly int page;

    public PageTemplate(int page, int pageSize)
    {
        this.page = page;
        this.pageSize = pageSize;
    }
    public abstract IEnumerable<T> Fetch();
}

/// <summary>
/// Design a MyDto Page object, Here you are using the Template method
/// </summary>
public abstract class MyDtoPageTemplate : PageTemplate<CustomerOrder>
{
    public MyDtoPageTemplate(int page, int pageSize) : base(page, pageSize) { }
}

/// <summary>
/// You can use ado.net for full performance or create a derivated class of MyDtoPageTemplate to use Dapper
/// </summary>
public sealed class SqlPage : MyDtoPageTemplate
{
    private readonly string _connectionString;
    public SqlPage(int page, int pageSize, string connectionString) : base(page, pageSize)
    {
        _connectionString = connectionString;
    }

    public override IEnumerable<CustomerOrder> Fetch()
    {
        using (var connection = new SqlConnection(_connectionString))
        {
            //This can be injected from contructor or encapsulated here, use a Stored procedure, is fine
            string commandText = "Select Something";
            using (var command = new SqlCommand(commandText, connection))
            {
                connection.Open();
                using (var reader = command.ExecuteReader())
                {
                    if (reader.HasRows) yield break;
                    while (reader.Read())
                    {
                        yield return new CustomerOrder()
                        {
                            Id = reader.GetInt32(0),
                            Name = reader.GetString(1),
                            Value = reader.GetString(2)
                        };
                    }
                }
            }
        }
    }
}

public sealed class MongoDbPage : MyDtoPageTemplate
{
    private readonly string _connectionString;
    public MongoDbPage(int page, int pageSize, string connectionString) : base(page, pageSize)
    {
        _connectionString = connectionString;
    }

    public override IEnumerable<CustomerOrder> Fetch()
    {
        //Return From CustomerOrder from mongoDb
        throw new NotImplementedException();
    }
}

/// <summary>
/// You can test and mock the fetcher
/// </summary>
public sealed class TestPage : IFetch<CustomerOrder>
{
    public IEnumerable<CustomerOrder> Fetch()
    {
        yield return new CustomerOrder() { Id = 0, Name = string.Empty, Value = string.Empty };
        yield return new CustomerOrder() { Id = 1, Name = string.Empty, Value = string.Empty };
    }
}

public class AppCode
{
    private readonly IFetch<CustomerOrder> fetcher;
    /// <summary>
    /// From IoC, inject a fetcher object
    /// </summary>
    /// <param name="fetcher"></param>
    public AppCode(IFetch<CustomerOrder> fetcher)
    {
        this.fetcher = fetcher;
    }
    public IEnumerable<CustomerOrder> FetchDtos()
    {
        return fetcher.Fetch();
    }
}

public class CustomController
{
    private readonly string connectionString;

    public void RunSql()
    {
        var fetcher = new SqlPage(1, 10, connectionString);
        var appCode = new AppCode(fetcher);
        var dtos = appCode.FetchDtos();
    }

    public void RunTest()
    {
        var fetcher = new TestPage();
        var appCode = new AppCode(fetcher);
        var dtos = appCode.FetchDtos();
    }
}

【讨论】:

    猜你喜欢
    • 2011-03-06
    • 1970-01-01
    • 2011-09-19
    • 2020-01-07
    • 2013-03-28
    • 2017-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多