如何构建C#控制台应用程序以有效使用IDisposable数据库资源?

时间:2020-12-26 20:34:49

Here's my proposed (very simplified to illustrate the problem space) design for a C# console application. The database connections implement IDisposable, and this solution doesn't allow for using the database connection objects. Can someone propose a more correct structure for a console application? This is a problem I need to solve often.

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

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
    }
}

Edit:

Some people can't figure out why I'd want them as member variables. Here's my use case (a little psuedocoded) of what would go in doDatabaseWork:

有些人无法弄清楚为什么我希望它们作为成员变量。这是doDatabaseWork中的用例(有点伪问题):

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

See how I'd want to keep these connections open for the life of this loop?

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

5 个解决方案

#1


I think that the best solution is to extract main logic from Program class. The Program class is some kind of starter for primary work. And providing wrappers for SqlConnections is not a good idea indeed, because they are managed resources already, wrapping them is redundant. Thus my solution looks like this:

我认为最好的解决方案是从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
    }
}

#2


How about writing a class that implements IDisposable.

如何写一个实现IDisposable的类。

Inside your class constructor, you can instantiate your DB connections.

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

Then inside your IDisposable.Dispose Method, you write your tear down code for closing your DB connections.

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

Here is a code sample to demonstrate what I mean:

这是一个代码示例,用于演示我的意思:

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();
        }
    }
}

And then, from within your main method of your Program class:

然后,从您的Program类的main方法中:

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

#3


Scott's answer is one way to do it. You could also consider using try{} finally instead?

斯科特的答案是一种方法。您还可以考虑最后使用try {}吗?

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();
    }
}

#4


Personally, I think you are over thinking this and the code samples in this thread are overly complex imho. I have no idea why people are implementing IDisposable on their Program class either since it's disposed when it exits.

就个人而言,我认为你已经在考虑这个问题了,这个帖子中的代码示例过于复杂了。我不知道为什么人们在他们的Program类上实现IDisposable,因为它在它退出时被处理掉了。

I can't think of a single reason to not use or why you cannot use the using(){} statement.

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

You want to open a Connection and hold it? Why? All the real connections are behind the scenes in .net connection pooling, so new'ing Connection objects is not a big deal. Just open and close as you need them and connection pooling handles all that behind the scenes.

你想打开一个Connection并持有它吗?为什么?所有真正的连接都在.net连接池的幕后,所以新的Connection对象并不是什么大问题。只需在需要时打开和关闭,连接池就可以处理幕后的所有操作。

I edited my example to wrap it in a class so you can have your encapsulation as well.

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

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


Hmm, I see no one has mentioned doing it this way. You don't have to have the variables that are used in the using declared locally.

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


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
    }
}

#1


I think that the best solution is to extract main logic from Program class. The Program class is some kind of starter for primary work. And providing wrappers for SqlConnections is not a good idea indeed, because they are managed resources already, wrapping them is redundant. Thus my solution looks like this:

我认为最好的解决方案是从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
    }
}

#2


How about writing a class that implements IDisposable.

如何写一个实现IDisposable的类。

Inside your class constructor, you can instantiate your DB connections.

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

Then inside your IDisposable.Dispose Method, you write your tear down code for closing your DB connections.

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

Here is a code sample to demonstrate what I mean:

这是一个代码示例,用于演示我的意思:

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();
        }
    }
}

And then, from within your main method of your Program class:

然后,从您的Program类的main方法中:

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

#3


Scott's answer is one way to do it. You could also consider using try{} finally instead?

斯科特的答案是一种方法。您还可以考虑最后使用try {}吗?

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();
    }
}

#4


Personally, I think you are over thinking this and the code samples in this thread are overly complex imho. I have no idea why people are implementing IDisposable on their Program class either since it's disposed when it exits.

就个人而言,我认为你已经在考虑这个问题了,这个帖子中的代码示例过于复杂了。我不知道为什么人们在他们的Program类上实现IDisposable,因为它在它退出时被处理掉了。

I can't think of a single reason to not use or why you cannot use the using(){} statement.

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

You want to open a Connection and hold it? Why? All the real connections are behind the scenes in .net connection pooling, so new'ing Connection objects is not a big deal. Just open and close as you need them and connection pooling handles all that behind the scenes.

你想打开一个Connection并持有它吗?为什么?所有真正的连接都在.net连接池的幕后,所以新的Connection对象并不是什么大问题。只需在需要时打开和关闭,连接池就可以处理幕后的所有操作。

I edited my example to wrap it in a class so you can have your encapsulation as well.

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

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


Hmm, I see no one has mentioned doing it this way. You don't have to have the variables that are used in the using declared locally.

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


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
    }
}