【问题标题】:Generic multi-layer data access pattern?通用的多层数据访问模式?
【发布时间】:2010-06-03 21:15:10
【问题描述】:

我一直在尝试一些用于 n 层数据访问的新模式,并且遇到了一种看起来非常灵活且易于实现的模式。基本上,我需要一个可以使各种数据层即时可插拔/可交换的解决方案——即,从数据库访问基础数据、分布式缓存、本地缓存等。

下面的代码很容易重复使用并且非常高效 - 只比我之前的完全硬编码的解决方案长了几个滴答声。

这看起来怎么样?有什么办法可以更好地实施吗?有什么一般的想法或批评吗?那些使用过类似模式的人有什么意见吗?

基类:

public class DataAdapterFactory<T> where T : class
{
    private DataAdapter<T> Adapter;
    public DataAdapterFactory(DataAdapterBase<T> Base)
    {
        Adapter = Base;
    }
    public void Extend<U>() where U : DataAdapterExtender<T>, T, new()
    {
        DataAdapterExtender<T> Extender = new U();
        Extender.BaseAdapter = Adapter as T;
        Adapter = Extender;
    }
    public T GetAdapter()
    {
        return Adapter as T;
    }
}
public class DataAdapter<T> where T : class { }
public class DataAdapterBase<T> : DataAdapter<T> where T : class { }
public class DataAdapterExtender<T> : DataAdapter<T> where T : class
{
    public T BaseAdapter;
}

在 DAL 中实施:

// base interface defines methods
public interface IMyDataAdapter
{
    string GetString();
}
// base sql adapter
public class SqlDataAdapter : DataAdapterBase<IMyDataAdapter>, IMyDataAdapter
{
    public string GetString()
    {
        return "SQL";
    }      
}
// provides cache support
public class DistributedCacheExtender : DataAdapterExtender<IMyDataAdapter>, IMyDataAdapter
{
    public string GetString()
    {
        return BaseAdapter.GetString() + ", Distributed Cache";
    }   
}
// provides local cache support
public class LocalCacheExtender : DataAdapterExtender<IMyDataAdapter>, IMyDataAdapter
{
    public string GetString()
    {
        return BaseAdapter.GetString() + ", Local Cache";
    }
}

访问数据:

public IMyDataAdapter GetAdapter() 
{
    // create adapter based on SqlDataAdapter
    DataAdapterFactory<IMyDataAdapter> factory = new DataAdapterFactory<IMyDataAdapter>(new SqlDataAdapter());
    // implement distributed cache
    factory.Extend<DistributedCacheExtender>();
    // implement local cache
    factory.Extend<LocalCacheExtender>();
    return factory.GetAdapter();
}

使用上面的工厂,任何基本适配器和扩展器的组合(必须按执行顺序调用Extend())都可以通过接口轻松无缝地使用,而业务层一无所知实施。

在上面的这种情况下,调用 GetString() 将导致“SQL,分布式缓存,本地缓存”。在现实世界的场景中,将首先调用本地缓存。如果某个项目不存在,我们将前往分布式缓存,如果它不存在,我们将从数据库中获取它 - 任何模块都可以根据需要根据需要换入或换出、用户等。

【问题讨论】:

    标签: c# design-patterns generics architecture


    【解决方案1】:

    我会为此查看http://en.wikipedia.org/wiki/Decorator_pattern - 您的示例将变成这样:

    public interface IMyDataAdapter
    {
        string GetString();
    }
    
    public class SqlDataAdapter :  IMyDataAdapter
    {
        public string GetString()
        {
            return "SQL";
        }      
    }
    
    public class LocalCacheDecorator : IMyDataAdapter
    {
        private IMyDataAdapter adapter;
        public LocalCacheDecorator(IMyDataAdapter adapter)
        {
            this.adapter = adapter;
        }
    
        public string GetString()
        {
            return "Local cache, " + this.adapter.GetString();
        }
    }
    

    【讨论】:

    • 绝对是一个选择。最初让我远离的原因是在你实现一个新的“装饰器”时需要编写构造函数,但我可能只是在挑剔......
    • 这种方法有利于组合而不是继承,如果您使用依赖注入,则效果很好。
    【解决方案2】:

    您正在做的事情看起来相当合理,尽管看看这些类在它们所在的命名空间和程序集方面是如何相关的会很有帮助。

    我的经验是抽象出接口背后的数据提供者,数据提供者(有时是接口)存在于单独的程序集中(因此:1 个用于 BL,1 个用于接口,1 个用于每个数据提供者)。

    我围绕业务概念(如 IPageDataProvider、ICustomerDataProvider 等)而不是数据源(即 db 表)定义接口,并且您在设计接口时要注意Interface Segeration Principle

    我在运行时通过工厂加载所需的数据提供者;这使用Activator.CreateInstance 方法。工厂从配置中得到它的指令。

    因此,当我想在业务逻辑中使用数据时,我会通过工厂(一行代码)创建所需接口实现的实例。

    使用这种方法,没有提供者必须使用的基类,但如果您愿意,显然您可以在数据提供者中使用基类。

    【讨论】:

      【解决方案3】:

      我想出了一种混合抽象工厂,可以满足您的目的,它还向开发人员隐藏了连接细节。它提供了一组连接,在我们的例子中,每组需要 4 个。这四个将由“连接集”工厂返回,还有一个“自定义连接集工厂”,您可以随时更改返回的连接。您使用代理访问连接对象。 然后我通过单例访问代理,这样我可以在我的应用程序加载事件或 global.asmx 中设置它,然后很容易交换你正在使用的连接。尽管您可以在运行时进行交换。希望这会有所帮助。

      它是专门为我的场景编写的,所以对你来说可能有点矫枉过正?

      请注意,这是针对 npgsql 的,您可以轻松地将其更改为标准的 .data。客户端基类,或 sqlclent 类...

          Public MustInherit Class DBConnectionDetail
          'note this abstract class could be an interface if you didn't want these common methods
      
          Protected _conStrBldr As New Npgsql.NpgsqlConnectionStringBuilder
          Protected _connection As Npgsql.NpgsqlConnection
      
          Public Sub New()
              'Set the connection builder properties in the subclass
          End Sub
      
          Friend ReadOnly Property ConnectionStringBuilder() As Npgsql.NpgsqlConnectionStringBuilder
              Get
                  Return _conStrBldr
              End Get
          End Property
      
      
          Friend Property Connection() As Npgsql.NpgsqlConnection
              Get
                  Return _connection
              End Get
              Set(ByVal value As Npgsql.NpgsqlConnection)
                  _connection = value
              End Set
          End Property
      
          ' Misc properties - information for programmers of higher layers
          Public MustOverride ReadOnly Property Description() As String
          Public MustOverride ReadOnly Property HostName() As String
          Public MustOverride ReadOnly Property IP() As String
          Public MustOverride ReadOnly Property Database() As String
      End Class
      
      
      
       Public Class LocalArchiveConnectionDetails
          Inherits DBConnectionDetail
      
      
          Public Sub New()
              _conStrBldr.Host = "localhost"
              _conStrBldr.Port = 5432
              _conStrBldr.UserName = "usr"
              _conStrBldr.Password = "pwd"
              _conStrBldr.Database = "archive"
              '_conStrBldr.Pooling = True
              '_conStrBldr.MinPoolSize = 5
              '_conStrBldr.MaxPoolSize = 10
              '_conStrBldr.CommandTimeout = 1024
              '_conStrBldr.Timeout = 1024
              '_conStrBldr.ConnectionLifeTime = 2
          End Sub
      
      
          Public Overrides ReadOnly Property Description() As String
              Get
                  Return "Local Connection to Database"
              End Get
          End Property
      
          Public Overrides ReadOnly Property Database() As String
              Get
                  Return "archive"
              End Get
          End Property
      
          Public Overrides ReadOnly Property HostName() As String
              Get
                  Return "local host"
              End Get
          End Property
      
          Public Overrides ReadOnly Property IP() As String
              Get
                  Return "127.0.0.1"
              End Get
          End Property
      End Class
      
      Public Interface IConnectionFactory
      
          ReadOnly Property GetMasterConnection() As DBConnectionDetail
          ReadOnly Property GetWarehouseConnection() As DBConnectionDetail
          ReadOnly Property GetArchiveConnection() As DBConnectionDetail
          ReadOnly Property GetAuditConnection() As DBConnectionDetail
      
      
      End Interface
      
          Public Class DBConnectionBuilder
      
          Friend Shared Function GetConnection(ByVal conStrBldr As DBConnectionDetail) As NpgsqlConnection
              Return New NpgsqlConnection(conStrBldr.ConnectionStringBuilder.ConnectionString)
          End Function
      
          'Friend Shared Function GetConnection(ByVal conStr As String) As NpgsqlConnection
          '    Return New NpgsqlConnection(conStr)
          'End Function
      
      End Class
      
          Public Class LocalConnectionFactory
          Implements IConnectionFactory
      
      
          Public ReadOnly Property GetArchiveConnection() As DBConnectionDetail Implements IConnectionFactory.GetArchiveConnection
              Get
                  Dim dbConnection As New LocalArchiveConnectionDetails
                  dbConnection.Connection = DBConnectionBuilder.GetConnection(dbConnection)
                  Return dbConnection
              End Get
          End Property
      
          Public ReadOnly Property GetMasterConnection() As DBConnectionDetail Implements IConnectionFactory.GetMasterConnection
              Get
                  Dim dbConnection As New LocalMasterConnectionDetails
                  dbConnection.Connection = DBConnectionBuilder.GetConnection(dbConnection)
                  Return dbConnection
              End Get
          End Property
      
          Public ReadOnly Property GetWarehouseConnection() As DBConnectionDetail Implements IConnectionFactory.GetWarehouseConnection
              Get
                  Dim dbConnection As New LocalWarehouseConnectionDetails
                  dbConnection.Connection = DBConnectionBuilder.GetConnection(dbConnection)
                  Return dbConnection
              End Get
          End Property
      
          Public ReadOnly Property GetAuditConnection() As DBConnectionDetail Implements IConnectionFactory.GetAuditConnection
              Get
                  Dim dbConnection As New LocalAuditConnectionDetails
                  dbConnection.Connection = DBConnectionBuilder.GetConnection(dbConnection)
                  Return dbConnection
              End Get
          End Property
      End Class
      
       ''' <summary>
      ''' The custom connection factory allows higher layers to decide which connection will be returned by the connection proxy
      ''' </summary>
      ''' <remarks></remarks>
      Public Class CustomConnectionFactory
          Implements IConnectionFactory
      
          Private _archiveConnection As DBConnectionDetail
          Private _masterConnection As DBConnectionDetail
          Private _warehouseConnection As DBConnectionDetail
          Private _auditConnection As DBConnectionDetail
      
          Friend Sub New()
      
          End Sub
      
          Friend Sub New(ByVal masterConnection As DBConnectionDetail, ByVal archiveConnection As DBConnectionDetail, _
                         ByVal warehouseConnection As DBConnectionDetail, ByVal auditConnection As DBConnectionDetail)
              _masterConnection = masterConnection
              _archiveConnection = archiveConnection
              _warehouseConnection = archiveConnection
              _auditConnection = auditConnection
          End Sub
      
          Friend Sub SetMasterConnectionDetail(ByVal connectionDetail As DBConnectionDetail)
              _masterConnection = connectionDetail
          End Sub
          Friend Sub SetArchiveConnectionDetail(ByVal connectionDetail As DBConnectionDetail)
              _archiveConnection = connectionDetail
          End Sub
          Friend Sub SetWarehouseConnectionDetail(ByVal connectionDetail As DBConnectionDetail)
              _warehouseConnection = connectionDetail
          End Sub
          Friend Sub SetAuditConnectionDetail(ByVal connectionDetail As DBConnectionDetail)
              _auditConnection = connectionDetail
          End Sub
      
          Public ReadOnly Property GetArchiveConnection() As DBConnectionDetail Implements IConnectionFactory.GetArchiveConnection
              Get
                  _archiveConnection.Connection = DBConnectionBuilder.GetConnection(_archiveConnection)
                  Return _archiveConnection
              End Get
          End Property
      
          Public ReadOnly Property GetMasterConnection() As DBConnectionDetail Implements IConnectionFactory.GetMasterConnection
              Get
                  _masterConnection.Connection = DBConnectionBuilder.GetConnection(_masterConnection)
                  Return _masterConnection
              End Get
          End Property
      
          Public ReadOnly Property GetWarehouseConnection() As DBConnectionDetail Implements IConnectionFactory.GetWarehouseConnection
              Get
                  _warehouseConnection.Connection = DBConnectionBuilder.GetConnection(_warehouseConnection)
                  Return _warehouseConnection
              End Get
          End Property
      
          Public ReadOnly Property GetAuditConnection() As DBConnectionDetail Implements IConnectionFactory.GetAuditConnection
              Get
                  _auditConnection.Connection = DBConnectionBuilder.GetConnection(_auditConnection)
                  Return _auditConnection
              End Get
          End Property
      End Class
      
       Public Class DBConnectionsProxy
      
          Private _ConnectionsFactory As IConnectionFactory
          Private _CurrentConnectionsFactory As IConnectionFactory
      
          Public Sub New(ByVal connectionFactory As IConnectionFactory)
              'check that a connection factory is provided otherwise nothing will work
              If connectionFactory Is Nothing Then
                  Throw New NullReferenceException("You must provide a connection factory")
              Else
                  _ConnectionsFactory = connectionFactory
                  _CurrentConnectionsFactory = connectionFactory
              End If
          End Sub
      
          Friend Property ConnectionFactory() As IConnectionFactory
              Get
                  Return _CurrentConnectionsFactory
              End Get
              Set(ByVal value As IConnectionFactory)
                  _CurrentConnectionsFactory = value
              End Set
          End Property
      
          Public ReadOnly Property GetMasterConnection() As Npgsql.NpgsqlConnection
              Get
                  Return _CurrentConnectionsFactory.GetMasterConnection.Connection
              End Get
          End Property
      
          Public ReadOnly Property GetArchiveConnection() As Npgsql.NpgsqlConnection
              Get
                  Return _CurrentConnectionsFactory.GetArchiveConnection.Connection
              End Get
          End Property
      
          Public ReadOnly Property GetWarehouseConnection() As Npgsql.NpgsqlConnection
              Get
                  Return _CurrentConnectionsFactory.GetWarehouseConnection.Connection
              End Get
          End Property
      
          Public ReadOnly Property GetAuditConnection() As Npgsql.NpgsqlConnection
              Get
                  Return _CurrentConnectionsFactory.GetAuditConnection.Connection
              End Get
          End Property
      
      
          ''' <summary>
          ''' Reset current connection factory to original connection factory this proxy was instantiated with
          ''' </summary>
          ''' <remarks></remarks>
          Public Sub ResetConnection()
              _CurrentConnectionsFactory = _ConnectionsFactory
          End Sub
      
          ''' <summary>
          ''' Changes the master connection returned for the current connection factory
          ''' </summary>
          ''' <param name="connectionDetail">Connection information for master database</param>
          ''' <remarks></remarks>
          Public Sub SetMasterConnection(ByVal connectionDetail As DBConnectionDetail)
              Me.SetAllConnections(connectionDetail, _CurrentConnectionsFactory.GetArchiveConnection, _
                                   _CurrentConnectionsFactory.GetWarehouseConnection, _CurrentConnectionsFactory.GetAuditConnection)
          End Sub
      
          ''' <summary>
          ''' Changes the archive connection returned for the current connection factory
          ''' </summary>
          ''' <param name="connectionDetail">Connection information for archive database</param>
          ''' <remarks></remarks>
          Public Sub SetArchiveConnection(ByVal connectionDetail As DBConnectionDetail)
              Me.SetAllConnections(_CurrentConnectionsFactory.GetMasterConnection, connectionDetail, _
                                   _CurrentConnectionsFactory.GetWarehouseConnection, _CurrentConnectionsFactory.GetAuditConnection)
          End Sub
      
          ''' <summary>
          ''' Changes the warehouse connection returned for the current connection factory
          ''' </summary>
          ''' <param name="connectionDetail">Connection information for warehouse database</param>
          ''' <remarks></remarks>
          Public Sub SetWarehouseConnection(ByVal connectionDetail As DBConnectionDetail)
              Me.SetAllConnections(_CurrentConnectionsFactory.GetMasterConnection, _CurrentConnectionsFactory.GetArchiveConnection, _
                                   connectionDetail, _CurrentConnectionsFactory.GetAuditConnection)
          End Sub
      
          ''' <summary>
          ''' Changes the audit connection returned for the current connection factory
          ''' </summary>
          ''' <param name="connectionDetail">Connection information for audit database</param>
          ''' <remarks></remarks>
          Public Sub SetAuditConnection(ByVal connectionDetail As DBConnectionDetail)
              Me.SetAllConnections(_CurrentConnectionsFactory.GetMasterConnection, _CurrentConnectionsFactory.GetArchiveConnection, _
                                   _CurrentConnectionsFactory.GetWarehouseConnection, connectionDetail)
          End Sub
      
      
          ''' <summary>
          ''' Sets the current connection factory to a custom connection factory using the supplied connection
          ''' </summary>
          ''' <param name="masterConnectionDetail">Connection information for master database</param>
          ''' <param name="archiveConnectionDetail">Connection information for archive database</param>
          ''' <param name="warehouseConnectionDetail">Connection information for warehouse database</param>
          ''' <remarks></remarks>
          Public Sub SetAllConnections(ByVal masterConnectionDetail As DBConnectionDetail, _
                                       ByVal archiveConnectionDetail As DBConnectionDetail, _
                                       ByVal warehouseConnectionDetail As DBConnectionDetail, _
                                       ByVal auditConnectionDetail As DBConnectionDetail)
      
              Dim customConnFactory As New CustomConnectionFactory
              customConnFactory.SetMasterConnectionDetail(masterConnectionDetail)
              customConnFactory.SetArchiveConnectionDetail(archiveConnectionDetail)
              customConnFactory.SetWarehouseConnectionDetail(warehouseConnectionDetail)
              customConnFactory.SetAuditConnectionDetail(auditConnectionDetail)
      
              _CurrentConnectionsFactory = customConnFactory
          End Sub
      
      
      End Class
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-07-18
        • 2014-10-01
        • 2010-11-06
        • 2015-02-27
        • 2018-07-31
        相关资源
        最近更新 更多