使应用程序数据库不可知

时间:2017-08-11 13:42:58

标签: c# asp.net design-patterns

我有一个现有的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)
    {
    }
}

然后我有一个Factory类,它负责实例化相应的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();               
        }
    }
}

Business Logic将仅与ConnectionManager对象进行交互,从而完全从中抽象出数据库。

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

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

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

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

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

我可以遵循任何设计模式或技术吗?

2 个答案:

答案 0 :(得分:2)

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

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在无SQL数据库上根本不起作用。因此,每个实现应该负责为其相应的数据库使用正确的交互,并且您的消费应用程序不应该知道它是如何工作的,因为它应该始终通过接口访问数据库,而不是通过具体的实现。 / p>

答案 1 :(得分:0)

我认为梅森的最后一个答案是通过良好的设计向前迈出了一步。我会在OO设计中更进一步,并尝试将其视为Dto的对象页面并返回一系列内容。我要做的改变是将方法的参数封装在对象中并通过构造函数注入它们,这样你就会有多个clases实现查询的抽象(1个类用于分页序列,其他类用于搜索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();
    }
}