【问题标题】:passing DB Connection object to methods将 DB Connection 对象传递给方法
【发布时间】:2008-10-30 20:18:39
【问题描述】:

想知道是否建议将数据库连接对象传递给(到其他模块)或让方法(在其他模块中)负责设置它。我倾向于让方法设置它,以便在使用它之前不必检查连接的状态,并且只需让调用者将任何需要的数据传递给设置连接所需的调用方法。

【问题讨论】:

    标签: c# .net ado.net database-connection


    【解决方案1】:

    我个人喜欢使用紧密范围的连接;晚点打开它们,使用它们,然后关闭它们(在“使用”块中,都在本地方法中)。在大多数情况下,连接池将处理重用连接,因此这种方法没有真正的开销。

    传递连接的主要优点使用是为了让您可以传递交易;但是,TransactionScope 是一种在方法之间共享事务的更简单的方法。

    由于这些类是特定于实现的,我会编写每个类来打开它自己的本机事务。否则,您可以使用 ado.net 工厂方法从配置文件(提供程序名称)创建适当的类型。

    【讨论】:

    • 如果您使用 TransactionScope 在使用不同连接的方法之间共享事务,则该事务将成为具有相关开销的分布式事务。因此,通常最好在方法之间传递连接(及其事务),同时保持范围紧凑。
    • 这是我喜欢在这些东西上使用 TLS 的另一个原因
    • 这是我们通常的做法,也是我喜欢的方式。
    • 好吧,如果你想在两个连接之间使用单个事务,那么它必须是分布式事务。所以这并不是真正的缺点。
    • @MarcGravell 即使你有很多对象?例如传递 List 对象的 IShop...每个 IItem 都应该有连接字符串并打开它自己的连接?
    【解决方案2】:

    就个人而言,我喜欢使用 SetData 和 GetData 在 Thread Local Storage 之上存储我当前打开的连接和事务的堆栈。我定义了一个类来管理我与数据库的连接并允许它使用 dispose 模式。这节省了我传递连接和事务的需要,我认为这会使代码变得混乱和复杂。

    我强烈建议反对将其留给每次需要数据时打开连接的方法。这将导致一个非常糟糕的情况,即很难在整个应用程序中管理事务并且打开和关闭太多连接(我知道连接池,从池中查找连接仍然比它更昂贵重用一个对象)

    所以我最终得到了这些方面的东西(完全未经测试):

    class DatabaseContext : IDisposable {
    
        List<DatabaseContext> currentContexts;
        SqlConnection connection;
        bool first = false; 
    
        DatabaseContext (List<DatabaseContext> contexts)
        {
            currentContexts = contexts;
            if (contexts.Count == 0)
            {
                connection = new SqlConnection(); // fill in info 
                connection.Open();
                first = true;
            }
            else
            {
                connection = contexts.First().connection;
            }
    
            contexts.Add(this);
        }
    
       static List<DatabaseContext> DatabaseContexts {
            get
            {
                var contexts = CallContext.GetData("contexts") as List<DatabaseContext>;
                if (contexts == null)
                {
                    contexts = new List<DatabaseContext>();
                    CallContext.SetData("contexts", contexts);
                }
                return contexts;
            }
        }
    
        public static DatabaseContext GetOpenConnection() 
        {
            return new DatabaseContext(DatabaseContexts);
        }
    
    
        public SqlCommand CreateCommand(string sql)
        {
            var cmd = new SqlCommand(sql);
            cmd.Connection = connection;
            return cmd;
        }
    
        public void Dispose()
        {
            if (first)
            {
                connection.Close();
            }
            currentContexts.Remove(this);
        }
    }
    
    
    
    void Test()
    {
        // connection is opened here
        using (var ctx = DatabaseContext.GetOpenConnection())
        {
            using (var cmd = ctx.CreateCommand("select 1"))
            {
                cmd.ExecuteNonQuery(); 
            }
    
            Test2(); 
        }
        // closed after dispose
    }
    
    void Test2()
    {
        // reuse existing connection 
        using (var ctx = DatabaseContext.GetOpenConnection())
        {
            using (var cmd = ctx.CreateCommand("select 2"))
            {
                cmd.ExecuteNonQuery();
            }
        }
        // leaves connection open
    }
    

    【讨论】:

    • 出于好奇,是什么阻止了 Test2 中的 Dispose() 调用关闭在 Test 中打开的连接?我猜 DatabaseContext 必须跟踪请求连接的客户端数量,并且只允许 Dispose 在仅打开 1 个请求时关闭连接。因此,Dispose 也会减少 DatabaseContext 中的客户端数量。似乎是过度耦合的机会(可能会被修复)。另外,当您需要跨线程共享 tran 时会发生什么?
    • @xero,跨线程传输是一种相当罕见的情况,因此您需要手动处理。这是一个相当危险的游戏。当您创建一个上下文时,它知道它是否是连接所有者,然后 dispose 行为适当
    【解决方案3】:

    出于自动化测试目的,传递它通常更容易。这称为dependency injection

    当您需要编写测试时,您可以创建一个模拟数据库连接对象并传递它而不是真实的。这样一来,您的自动化测试就不会依赖于每次都需要重新填充数据的实际数据库。

    【讨论】:

    • 线程本地存储解决了这个问题,不需要在应用程序中的每个方法上添加两个额外的参数
    【解决方案4】:

    我个人努力尽可能集中我的数据访问,但是,如果不可能,我总是在其他类中打开一个新连接,因为我发现在传递时有太多其他事情可能会阻碍实际的连接对象。

    【讨论】:

      【解决方案5】:

      这里对这个问题有更深入的了解。我有一个管理数据库连接的类,并且有 2 个实现接口的类。其中一个类用于 SQL,另一个用于 OLAP。管理器知道要使用哪个连接,因此它可以将确切的连接传递给类型,或者类型可以创建自己的连接。

      【讨论】:

        【解决方案6】:

        您可以毫无问题地传递连接对象(例如,Microsoft Enterprise Library 允许在连接中传递静态方法调用),或者您可以在外部对其进行管理,这取决于您的设计,没有直接的技术折衷。

        如果您的解决方案将被移植到其他数据库,请注意可移植性,不要传递特定连接(这意味着不要传递您计划与其他数据库一起使用的 SqlConnection)

        【讨论】:

          【解决方案7】:

          设置连接可能很昂贵,并且可能会增加往返行程。所以,再一次,更好的设计是传递连接对象。

          我说可能,因为如果您是 Microsoft ADO 应用程序,您可能正在使用连接池....

          【讨论】:

          • 一般来说,pooling 可以很好地解决这个问题……而不仅仅是 web。
          【解决方案8】:

          我建议您区分连接对象及其状态(打开、关闭)。

          您可以有一个从 web.config 读取连接字符串的方法(或属性)。每次使用相同版本的连接字符串可确保您从连接池中受益。

          当您需要打开连接时调用该方法。在最后一刻,在设置完所有 SqlCommand 属性后,打开连接,使用它,然后关闭它。在 C# 中,您可以使用 using 语句来确保连接已关闭。如果没有,请务必在 finally 块中关闭连接。

          【讨论】:

            【解决方案9】:

            我会使用 web.config

            <configuration>
                <connectionStrings>
                    <add name="conn1" providerName="System.Data.SqlClient" connectionString="string here" />
                    <add name="conn2" providerName="System.Data.SqlClient" connectionString="string here" />
                </connectionStrings>
            </configuration>
            

            然后你可以从应用程序的任何地方引用它

            【讨论】:

            • 但我不希望每个部分都必须从配置中获取,有 1 个类来管理连接
            • 将连接字符串放在配置文件中不加密是否安全?
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-09
            • 2011-09-27
            • 2014-12-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多