【问题标题】:Best Practices: C# working with DB最佳实践:C# 使用 DB
【发布时间】:2015-04-06 01:57:58
【问题描述】:

首先,我是一名 Java 程序员,而且我是 C# 新手,我需要 C# 开发人员的意见。我正在开发一个连接到数据库的应用程序(firebird 1.5),查询一些数据并返回给我,所以没有什么复杂的,但不幸的是我遇到了一些事情:

我们知道数据库连接应该在单独的线程中实现,因为它是一个重量级的操作,所有的连接都应该在连接池中,以便重用已经打开的连接,而不是创建新的连接。

那么我的第一个问题来了 - 如何正确组织连接池? (我读过的连接池呢,通常连接池已经由数据提供者实现,我可以在连接参数中设置它,比如“connectionBuilder.Pooling = true;”)

查询呢?我的意思是我一直使用 Query per-Thread (我认为这是正确的,因为我们也执行高权重操作,我错了吗?无论如何我很高兴看到您组织数据库工作的最佳实践) 并且在 Java 中,我只是通过使用 interfacesanonymous classes 从单独的线程返回查询结果,如下所示:

DBHelper.class中(DBHelper是一个单例)

public interface QueryListener {

    public void onSuccess(ArrayList<?>);

    public void onError(Exception e);
}

public synchronized void getPromoActions(final QueryListener listener) {
    if (listener != null) {
      try {
        ArrayList<String> myPromoActions;
        .............
        // some query's code
        .....
        listener.onSucces(myPromoActions);
      } catch(Exception e) {
        listener.onError(e);
      } finally {
        closeDatabase();
      }
    }
}

在一些 UI 类中(例如 MainWindow

public void getPromoActions(){
  new Thread(new Runnable() {
    @Override
    public void run() {
      DBHelper.getInstance().getPromoActions(new QueryListener() {

        @Override
        public void onSuccess(ArrayList<?>) {
            // set Data to UI element such as Table
        }

        @Override
        public void onError(Exception e){
           // Handling exception
        }
      });
    }  
  }).start();
}

在 C# 中,我应该使用委托来标记哪个方法将在线程中执行,但我不能将任何回调作为参数发送 - 所以 我应该如何将我的查询结果返回到主 UI 线程

UPD

我对如何使用委托和事件有所了解,但在引发自定义事件时遇到了问题。我已经声明了一个 EventHandler 和一个自定义 EventArgs:

public delegate void QueryResultEventHandler(object sender,  QueryResultEventArgs e);

public class QueryResultEventArgs : EventArgs
{
    public List<String> QueryResult { get; set; }
    public int QueryRecordsCount { get; set; }
}

在我的 DBHelper.class 中,我声明了下一个字段和事件:

private QueryResultEventHandler _queryResult;

public event QueryResultEventHandler onQueryResult
{
  add
  {
    lock (this)
    {
      _queryResult += value;
    }
  }

  remove
  {
    lock (this)
    {
      _queryResult -= value;
    }
  }
}

UI 类(MainWindow)我使用下一个代码:

public void GetAllDistricts() {
        DBHelper.Instance.onQueryResult += new QueryResultEventHandler(GetAllDistricsResultHandler);
        DBHelper.Instance.GetAllDistricts();
    }

public void GetAllDistricsResultHandler(object sender, QueryResultEventArgs e){
        // Here I'm adding the query result to Table
    }

所以我现在的问题是如何异步引发事件?在我的 DBHelper.class 中,我正在尝试将 beginInvoke&endInvoke 与 _query 委托一起使用,但似乎我错过了一些代码行,无论是什么我都无法理解我做错了什么以及如何提高事件异步?这是我的 DBHelper.class 代码:

public void GetAllDistricts() {
  try
    {
      if (_queryResult != null)
      {
      //** This code should run asynchronously  ---------->

        using (FbConnection connection = GetConnection())
        {
          FbCommand getAllDistrictsCommand = new FbCommand();

          getAllDistrictsCommand.CommandText = "SELECT * FROM SEND";
          getAllDistrictsCommand.Connection = connection;

          QueryResultEventArgs args = new QueryResultEventArgs();
          using (FbDataReader reader = getAllDistrictsCommand.ExecuteReader())
          {
            while (reader.Read())
            {
             //Here must be the processing of query results and filling the
             //QueryResultEventArgs 
              args.QueryResult.Add(reader[0].ToString());
            }                    
            args.QueryRecordsCount = reader.GetInt32(reader.GetOrdinal("Rows"));

            // And here after sucessfull query I should call OnQueryResult()
            OnQueryResult(args);
          }
        }
      //**<--------------------
      }
      else
      {
        throw new Exception("...Some exception message...");
      }
  }
  catch (Exception e)
  {
    log.ErrorException(e.Message, e);
    throw new Exception("...Some exception message...");;
  }
  finally {
    CloseConnection();
  }
}

// The QueryResultEvent method
protected void OnQueryResult(QueryResultEventArgs e)
{
  if (_queryResult != null)
  {
    _queryResult(this, e);
  }
}

【问题讨论】:

  • 只有在架构允许的情况下,数据库连接池才会真正有益。如果您正在开发客户端服务器类型的应用程序,即直接连接到服务器的前端,那么连接池有点忽略了这一点。您是否直接从客户端应用程序连接到数据库。
  • @Namphibian 是的,我正在开发一个客户端-服务器类型的应用程序并直接连接到数据库。
  • 在您的情况下,添加连接池只会增加复杂性而没有任何好处。通常,连接池将位于应用程序服务器上。要获得连接池的好处,您需要迁移到 n 层架构。
  • 我应该如何将查询结果返回到主 UI 线程? 创建了相对较新的 async-await 模型,以使异步操作更容易和更清晰。它基本上创建了内联回调,因此您的(相对干净的)代码可以执行长时间运行的数据库查询而不会冻结您的 UI。您可以开始使用herehere
  • @Mike 回答了更新主 UI 线程的异步后台线程,对于您的另一个问题:如何正确组织连接池? 更常见的连接池不是数据提供者或编码具体,它与数据库有关。例如,SQL Server 当连接字符串相同时自动处理Connection Pooling

标签: c# multithreading database-connection


【解决方案1】:

首先是关于连接池。如果您将使用 ADO.NET,那么您不必担心这一点,因为它已经存在。你不需要做任何额外的工作,你只需创建一个连接:

using (var connection = new SqlConnection(connectionString))
{
    // Queries to DB
}

您应该始终关闭处理您的联系。方法的名称看起来“吓人”,但实际上连接被重用了。请阅读此MSDN article 以获取更多详细信息。

您提出的代码看起来过于复杂。我认为您应该考虑使用async/await 模式,它通常不是多线程的,但它可以处理 UI 响应问题并简化代码的编写/读取。在较新版本的 .NET 中,几乎所有可能长时间执行的方法都有异步版本。因此,例如,您的数据访问层可能看起来像这样(我使用 Dapper ORM's QueryAsync 方法只是为了保持代码简短和简单):

public async Task<IList<District>> GetAllDistrictsAsync()
{
    using (var connection = await GetConnectionAsync())
    {
        return (await connection.QueryAsync<District>("select * from Districts")).ToList();
    }
}

public async Task<IDbConnection> GetConnectionAsync()
{
    var connectionString = 
        ConfigurationManager.ConnectionStrings["DbConnectionString"].ConnectionString;
    var connection = new SqlConnection(connectionString);
    await connection.OpenAsync();
    return connection;
}

然后在 UI 上的某个地方:

private async void Button_Click(object sender, EventArgs e)
{
    var districts = await GetAllDistrictsAsync();
}

如果您仍然需要在不同的线程中执行一些代码,您应该查看Tasks 命名空间。

Task.Factory
    .StartNew<IList<District>>(GetAllDistricts)
    .ContinueWith(districts =>
    {
        // UI thread
    }, TaskScheduler.FromCurrentSynchronizationContext());

在此示例中,GetAllDistricts 不是异步的,而是在不同的线程中执行。但是ContinueWith会因为TaskScheduler.FromCurrentSynchronizationContext()而在UI线程中执行。

【讨论】:

  • 如果您有任何其他问题,我也可以添加答案(如果可以的话)。波扎卢伊斯塔 ;)
  • 请客气 druzhishe =),接下来解释一下 - 那么使用 Delegates&Events 进行异步操作是不是一种不好的做法?如果是,我对为什么感兴趣?问候。
  • 这是旧方法,异步是一个新功能,可以使它更容易。 async 通过将处理事件的逻辑全部保存在 1 个函数中并允许框架处理您不想阻塞 UI 线程但想在该线程中继续处理的事实,从而使您获得最佳可读性。如果您想与事件​​异步执行,您必须自己处理线程(包括将控制权返回给 UI 线程,因为您的 UI 不太可能是线程安全的 - 如何执行此操作取决于您使用的内容UI,如果您在 WPF 中,则通过 Dispatcher)。
  • 我同意@fyjham。使用较旧的技术也不错,在某些情况下,它们提供了更多的控制,可能是一个更可取的选择。但是随着 .NET 的每个版本,我们都会获得更酷、更简单的功能来处理困难的事情。因此,如果您从新项目开始并且没有任何遗留代码,为什么不让您的生活更轻松?
  • 上面的代码可以减少一点,但我仍然喜欢这个例子,因为它展示了如何处理异步打开连接。使用 QueryAsync 时可以使用关闭的连接,如果连接未打开,dapper 本身会打开连接。
【解决方案2】:
public void GetAllDistricts() {

        DBHelper.Instance.onQueryResult += 
                  new QueryResultEventHandler(GetAllDistricsResultHandler);

       new Thread(
           new ThreadStart(DBHelper.Instance.GetAllDistricts)
            ).Start();

    }

但是您将面临的问题是您将无法从 EventHandler 访问您的 UI 控件,因为它将被拒绝,因为您不再处于同一个线程中......

请参阅那篇文章以获得一些解释

How to update the GUI from another thread in C#?

为避免这种情况,您可以使用 BackgroundWorker 控件。

【讨论】:

    【解决方案3】:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-17
      • 2010-12-16
      • 2016-07-10
      • 2021-10-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多