代码之家  ›  专栏  ›  技术社区  ›  Kalpak

为.NET产品支持多个数据库的最佳方法是什么?

  •  11
  • Kalpak  · 技术社区  · 17 年前

    我们正在设计一种可以支持多个数据库的产品。我们目前正在做类似的事情,以便我们的代码支持MS SQL和MySQL:

    namespace Handlers
    {
        public class BaseHandler
        {
            protected string connectionString;
            protected string providerName;
    
            protected BaseHandler()
            {
                connectionString = ApplicationConstants.DatabaseVariables.GetConnectionString();
                providerName = ApplicationConstants.DatabaseVariables.GetProviderName();
            }
        }
    }
    
    namespace Constants
    {
        internal class ApplicationConstants
        {
            public class DatabaseVariables
            {
                public static readonly string SqlServerProvider = "System.Data.SqlClient";
                public static readonly string MySqlProvider = "MySql.Data.MySqlClient";
    
                public static string GetConnectionString()
                {
                    return ConfigurationManager.ConnectionStrings["CONNECTION_STRING"].ConnectionString; 
                }
    
                public static string GetProviderName()
                {
                    return ConfigurationManager.ConnectionStrings["CONNECTION_STRING"].ProviderName;
                }
            }
        }
    }
    
    namespace Handlers
    {
        internal class InfoHandler : BaseHandler
        {
            public InfoHandler() : base()
            {
            }
    
            public void Insert(InfoModel infoModel)
            {
                CommonUtilities commonUtilities = new CommonUtilities();
                string cmdInsert = InfoQueryHelper.InsertQuery(providerName);
                DbCommand cmd = null;
                try
                {
                    DbProviderFactory provider = DbProviderFactories.GetFactory(providerName);
                    DbConnection con = LicDbConnectionScope.Current.GetOpenConnection(provider, connectionString);
                    cmd = commonUtilities.GetCommand(provider, con, cmdInsert);
                    commonUtilities.PrepareCommand(cmd, infoModel.AccessKey, "paramAccessKey", DbType.String, false, provider, providerName);
                    commonUtilities.PrepareCommand(cmd, infoModel.AccessValue, "paramAccessValue", DbType.String, false, provider, providerName);
                    cmd.ExecuteNonQuery();
                }
                catch (SqlException dbException)
                {
                    //-2146232060 for MS SQL Server
                    //-2147467259 for MY SQL Server
                    /*Check if Sql server instance is running or not*/
                    if (dbException.ErrorCode == -2146232060 || dbException.ErrorCode == -2147467259)
                    {
                        throw new BusinessException("ER0008");
                    }
                    else
                    {
                        throw new BusinessException("GENERIC_EXCEPTION_ERROR");
                    }
                }
                catch (Exception generalException)
                {
                    throw generalException;
                }
                finally
                {
                    cmd.Dispose();
                }
            }
        }
    }
    
    namespace QueryHelpers
    {
        internal class InfoQueryHelper
        {
            public static string InsertQuery(string providerName)
            {
                if (providerName == ApplicationConstants.DatabaseVariables.SqlServerProvider)
                {
                    return @"INSERT INTO table1
               (ACCESS_KEY
               ,ACCESS_VALUE)
         VALUES
               (@paramAccessKey
               ,@paramAccessValue) ";
                }
                else if (providerName == ApplicationConstants.DatabaseVariables.MySqlProvider)
                {
                    return @"INSERT INTO table1
               (ACCESS_KEY
               ,ACCESS_VALUE)
         VALUES
               (?paramAccessKey
               ,?paramAccessValue) ";
                }
                else
                {
                    return string.Empty;
                }
            }
        }
    }
    

    如果有更好的方法可以吗?另外,这种方法的优点和缺点是什么?

    10 回复  |  直到 13 年前
        1
  •  2
  •   Danny Beckett    13 年前

    为了你目前的需要,我同意NHibernate…

    只想指出你的类层次结构…

    你最好使用界面

    例如(只需检查文档或Internet以获得准确的语法)

    Interface IDBParser  
        Function1  
        Function2  
    
    class MSSQLParser : IDBParser  
        Function1  
        Function2  
    
    class MySQLParser : IDBParser  
        Function1  
        Function2 
    

    然后在代码中可以使用接口

    Main()  
        IDBParser dbParser;  
        if(...)  
           dbParser = new MSSQLParser();  
        else  
           dbParser = new MySQLParser();  
    
        SomeFunction( dbParser );  
    
    // the parser can be sent by parameter, global setting, central module, ...  
        SomeFunction( IDBParser dbParser)  
          dbParser.Function1();  
    

    这样就更容易管理,并且您的代码不会充满相同的if/else条件。添加其他数据库也会容易得多。另一个优点是它可以通过发送模拟对象来帮助您进行单元测试。

        2
  •  10
  •   Juliet    17 年前

    不管你做什么, 不要 编写自己的映射代码。它以前已经做过了,而且可能比你能用手写的任何东西都好上百万倍。

    毫无疑问,你应该使用 NHibernate . 它是一个使数据库访问透明的对象关系映射器:您定义了一组表示数据库中每个表的DAL类,并使用nHibernate提供程序对数据库执行查询。nhibernate将动态生成查询数据库和填充DAL对象所需的SQL。

    nhibernate的好处在于,它基于配置文件中指定的内容生成SQL。开箱即用,它支持SQL Server、Oracle、MySQL、Firebird、Postgres和 a few other databases .

        3
  •  5
  •   Sorantis    17 年前

    我会用 NHibernate .

    这里很好 beginner tutorial

        4
  •  2
  •   quillbreaker    17 年前

    如果您必须自己编写代码,而不是使用提供统一访问的产品,请记住,像sqldataadapter和oracledataadapter这样的对象继承自通用的dbdataadapter(至少在运行时的更高版本中)。如果强制转换为dbdataadapter,则可以在对两个数据库执行相同操作的位置编写可用于两个数据库的代码。您的一些代码看起来有点像这样:

    DbDataAdapter adapter = GetOracleDataAdapter() as DbDataAdapter;
    

    一旦您强制执行了,不管它是sqldataadapter还是oracledataadapter。你也可以这样称呼它。

    但是,请记住,为两个数据库编码意味着使用只存在于两个数据库中的特性,同时必须解决这两个数据库的缺点。这不是个好主意。

        5
  •  2
  •   tobsen    17 年前

    如果您需要从数据库条目到对象的映射,我建议您使用其他已建议的解决方案:nhibernate。 如果这对您的应用程序来说是多余的,并且您希望使用ADO.NET方法,而不需要O/RM解决方案,那么您应该看看Spring.NET的家伙做了什么,并了解 Ado.Net Provider Abstraction.

        6
  •  1
  •   Scott Whitlock    17 年前

    有一些对象关系映射层将支持多种数据库技术,比如 Entity Spaces .

        7
  •  1
  •   Juri    17 年前

    在这种情况下,总是好的是创建一个分层的体系结构,其中所有与数据库相关的东西都在数据访问层中。然后您可以有不同的DAO层实现,一个用于Oracle、SQL Server等。

    您应该使用接口将业务层与DAO层分离,这样您的业务层就可以使用它们访问DAO层。因此,您可以完美地交换DAO层的底层实现,以便在OracleDB或您喜欢的任何系统上运行。

    另一个好的建议是看看Scott已经建议的对象关系映射器。我来看看NHibernate或实体框架。

        8
  •  0
  •   MusiGenesis    17 年前

    解决这个问题的一种方法是,设计应用程序以完全处理断开连接的数据集,并编写一个数据访问组件,用于处理从您将支持的不同数据库品牌获取数据,以及将应用程序对数据集所做的更改持久化回原始数据库。

    优点:NET中的数据集写得很好,易于使用,功能强大,并且为使用基于表的数据提供了方法和工具。

    缺点:如果应用程序需要在客户端处理非常大的数据集,那么这个方法可能会有问题。

        9
  •  0
  •   Rui Craveiro    17 年前

    现在,微软的实体框架有一些缺点,其中一些可能是交易破坏者,这取决于应用程序的预期架构。

    从我所看到和阅读的关于V2的内容来看,它将与.NET 4一起提供,我认为它肯定值得一看。

        10
  •  0
  •   ConcernedOfTunbridgeWells    17 年前

    许多人建议使用O/R映射框架,如NHibernate。这是一种非常合理的方法,除非您出于某种原因不想使用O/R映射器。像nhibernate这样的东西可能会给你带来95%以上的好处,但是你可能需要编写一些定制的SQL。如果是这样的话,不要惊慌;你仍然可以为其他人做一个特别的解决方案。

    在这种情况下,将确实需要自定义SQL的位分离到特定于平台的插件模块中。根据需要为您想要支持的单个数据库平台编写Oracle、MySQL、SQL Server(等)插件。

    ADO.NET使包装存储过程变得相当容易,因此您可能能够将依赖平台的层向下移动到某些存储过程中,从而向中间层提供或多或少一致的API。仍然存在一些平台依赖项(如SQL Server变量名上的“@”前缀),因此您需要建立一个通用存储过程包装机制(这并不那么困难)。

    如果你运气好的话 需要 以这种方式爆发的数量相当少,因此维护插件的工作量将受到限制。

    推荐文章