【问题标题】:How do I structure a C# console application to efficiently use IDisposable database resources?如何构建 C# 控制台应用程序以有效使用 IDisposable 数据库资源?
【发布时间】:2010-10-24 02:16:56
【问题描述】:

这是我为 C# 控制台应用程序提出的(非常简化以说明问题空间)设计。数据库连接实现 IDisposable,并且此解决方案不允许 using 数据库连接对象。有人可以为控制台应用程序提出更正确的结构吗?这是我需要经常解决的问题。

class Program 
{
    SQLiteConnection sourceConnection;
    SQLiteConnection destinationConnection;

    static void Main(string[] args)
    {
        Program shell = new Program();

        // get connection strings from command line arguments
        string sourceConnectionString = shell.getConnectionString(args);
        string destinationConnectionString = shell.getConnectionString(args);

        // call non-static methods that use
        shell.setUpConnections(sourceConnectionString, destinationConnectionString);

        shell.doDatabaseWork();
    }

    private void setUpConnections(string sourceConnectionString, string destinationConnectionString)
    {
        sourceConnection = new SQLiteConnection(sourceConnectionString);
        destinationConnection = new SQLiteConnection(destinationConnectionString);
    }

    private void doDatabaseWork()
    {
        // use the connections here
    }
}

编辑:

有些人不明白我为什么要将它们作为成员变量。这是我在 doDatabaseWork 中的用例(有点伪编码):

foreach (Row sourceRow in DBResultSet)
{
  string sourceXml = sourceRow.Columns["MyColumnName"].Value;
  string destinationXML = transformUsingXSLT(sourceXml);
  writeToDestination(destinationXml);
}

看看我想如何在这个循环的整个生命周期中保持这些连接打开?

【问题讨论】:

    标签: c# sqlite database-connection console-application


    【解决方案1】:

    编写一个实现 IDisposable 的类怎么样。

    在您的类构造函数中,您可以实例化您的数据库连接。

    然后在 IDisposable.Dispose 方法中,编写用于关闭数据库连接的拆卸代码。

    这是一个代码示例来说明我的意思:

    public class DBWrapper : IDisposable
    {
        public SqlConnection Connection1 { get; set; }
        public SqlConnection Connection2 { get; set; }
    
        public DBWrapper()
        {
            Connection1 = new SqlConnection();
            Connection1.Open();
            Connection2 = new SqlConnection();
            Connection2.Open();
        }
        public void DoWork()
        {
            // Make your DB Calls here
        }
    
        public void Dispose()
        {
            if (Connection1 != null)
            {
                Connection1.Dispose();
            }
            if (Connection2 != null)
            {
                Connection2.Dispose();
            }
        }
    }
    

    然后,在 Program 类的 main 方法中:

    class Program
    {
        static void Main(string[] args)
        {
            using (DBWrapper wrapper = new DBWrapper())
            {
                wrapper.DoWork();
            }
        }
    }
    

    【讨论】:

    • 对,但是我有两个数据库连接,它们需要能够从其中一个获取资源,对其进行转换,然后写入另一个。我不想每次读写都重新连接。我只想拥有一个打开的句柄,然后能够根据需要对每个句柄执行操作。我做 sourceWrapper.DoWork(destinationWrapper) 吗?
    • 尝试在该连接对象上打开 2 个或更多阅读器,看看为什么效果不佳
    • 我刚刚将我的代码示例更改为使用 2 个 Connection 对象更加明确 在 DoWork 方法中,您可以访问两个 SqlConnections,您可以做任何您需要的事情,而无需为每次读取重新连接和写。基本上,您将 DB 逻辑抽象到 DBWrapper.DoWork() 方法中,并远离您的 Program.Main 方法。
    • 那么当你有10个连接时,你会添加10个连接成员变量吗?我知道,练习/示例是人为设计的,并不是想给你带来困难
    • @Deviant,原始发布者有一个源连接和目标连接的示例。我想不出我可能需要超过 2 个连接对象的示例,但如果我这样做了,我可能会有一个包含连接对象列表的成员变量......但我们不要进一步分散模式的注意力。如果在执行 100,000 次迭代的紧密循环中调用 DoWork 方法会怎么样?您能看到不打开和关闭该循环内的连接的好处吗?我之前在 OleDbConnection 对象中使用过这种模式,并注意到性能提升很大。 :)
    【解决方案2】:

    我认为最好的解决方案是从 Program 类中提取主要逻辑。 Program 类是主要工作的某种启动器。并且为 SqlConnections 提供包装器确实不是一个好主意,因为它们已经是托管资源,包装它们是多余的。因此我的解决方案如下所示:

    class ProgramCore : IDisposable
    {
        internal ProgramCore(string sourceConnectionString, string destinationConnectionString)
        {
            setUpConnections(sourceConnectionString, destinationConnectionString);
        }
    
        internal void Execute()
        {
            // do whatever you want
            doDatabaseWork();
            // do whatever you want
        }
    
        public void Dispose()
        {
            if (_sourceConnection != null)
                _sourceConnection.Dispose();
            if (_destinationConnection != null)
                _destinationConnection.Dispose();
        }
    
        private void setUpConnections(string sourceConnectionString, string destinationConnectionString)
        {
            _sourceConnection = new SQLiteConnection(sourceConnectionString);
            _destinationConnection = new SQLiteConnection(destinationConnectionString);
        }
    
        private void doDatabaseWork()
        {
            // use the connections here
        }
    
        private SQLiteConnection _sourceConnection;
        private SQLiteConnection _destinationConnection;
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            // get connection strings from command line arguments
            string sourceConnectionString = GetConnectionString(args);
            string destinationConnectionString = GetConnectionString(args);
    
            using (ProgramCore core = new ProgramCore(sourceConnectionString, destinationConnectionString))
            {
                core.Execute();
            }
        }
    
        static string GetConnectionString(string[] args)
        {
            // provide parsing here
        }
    }
    

    【讨论】:

    • 连接取自连接池。因此,如果您不每秒打开和关闭它们 1000 次,它不会影响您的程序的性能。但是,如果您需要经常打开和关闭连接,那么您应该在程序中使用数据缓存。但这涉及优化,您应该仔细分析您的程序以优化它。记住 Donald Knuth 在优化方面所说的话:“我们应该忘记小的效率,说大约 97% 的时间:过早的优化是万恶之源”。
    • 很公平,但我的第一个策略是根据需要打开和关闭数据库连接。在脚本的生命周期中,它慢到爬行。我没有进行分析,但即使只是将写入分组为每个连接 1000 次的组,也可以管理性能。
    • 老实说,没有人支持你的策略,但到目前为止,这是最接近回答我的问题的策略。
    【解决方案3】:

    Scott 的回答是一种方法。您也可以考虑使用 try{} finally 代替?

    static void Main(string[] args)
    {
        Program shell = new Program();
    
        // get connection strings from command line arguments
        string sourceConnectionString = shell.getConnectionString(args);
        string destinationConnectionString = shell.getConnectionString(args);
    
        // call non-static methods that use
        shell.setUpConnections(sourceConnectionString, destinationConnectionString);
        try
        {
          shell.doDatabaseWork();
        }
        finally
        {
          if(sourceConnection != null)
            sourceConnection.Dispose();
          if(destinationConnection != null)
            destinationConnection.Dispose();
        }
    }
    

    【讨论】:

    • 为什么不直接使用 using 关键字作为 sourceConnection 和 destinationConnection 呢?
    • 这行得通吗?回答并出示有效的代码示例,Brian?
    【解决方案4】:

    就我个人而言,我认为你想多了,而且这个线程中的代码示例过于复杂恕我直言。我不知道为什么人们在他们的 Program 类上实现 IDisposable ,因为它在退出时被释放。

    我想不出一个不使用或为什么不能使用 using(){} 语句的原因。

    您想打开一个连接并保持它吗?为什么?所有真正的连接都在 .net 连接池的幕后,因此新建 Connection 对象并不是什么大问题。只需根据需要打开和关闭它们,连接池会在后台处理所有这些。

    我编辑了我的示例以将其包装在一个类中,这样您也可以进行封装。

    class Program 
    {
        static void Main(string[] args)
        {
            DBWorker worker = new DBWorker();
            worker.DoDatabaseWork();
        }
    }
    
    public class DBWorker 
    {
    
        private void DoDatabaseWork()
        {
            using (SQLiteConnection sourceDB = new SQLiteConnection( GetConnectionString() ))
            {
                sourceDB.Open();
                using (SQLiteConnection destDB = new SQLiteConnection( GetConnectionString() ))
                {
                    destDB.Open();
                }
            }
        }
    
    }
    

    【讨论】:

    • 我希望能够拥有单独的辅助方法来实际执行读取和写入操作,这样我就可以拥有在自己的方法中将数据从一个转换到另一个的逻辑。如果我不将数据存储为成员,那么我在需要时无法访问连接。
    【解决方案5】:

    嗯,我看没有人提到过这样做。您不必在本地声明 using 中使用的变量。

    
    class Program 
    {
        SQLiteConnection sourceConnection;
        SQLiteConnection destinationConnection;
    
        static void Main(string[] args)
        {
            Program shell = new Program();
    
            // get connection strings from command line arguments
            string sourceConnectionString = shell.getConnectionString(args);
            string destinationConnectionString = shell.getConnectionString(args);
    
            using (sourceConnection = new SQLiteConnection(sourceConnectionString))
            using (destinationConnection = new SQLiteConnection(destinationConnectionString))
            {
                shell.doDatabaseWork();
            }
        }
    
        private void doDatabaseWork()
        {
            // use the connections here
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-05-15
      • 2010-11-20
      • 2012-02-14
      • 1970-01-01
      • 2010-09-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多