最佳实践:C# 使用 DB

Best Practices: C# working with DB

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

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

所以这是我的第一个问题 - 如何正确组织连接池? (关于连接池,我读过通常连接池已经由数据提供者实现,我可以像 "connectionBuilder.Pooling = true;" 一样在连接参数中设置它)

查询呢?我的意思是我一直使用每线程查询(我认为这是正确的,因为我们也进行了高权重操作,我错了吗?无论如何,我很高兴看到您在组织数据库方面的最佳实践work) 和 Java 我只是做 return 通过使用 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-class中(例如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# 中,我应该使用委托来标记哪个方法将在线程中执行,但不幸的是我不能将任何回调作为参数发送 - 所以 我应该如何 return 我的查询结果到主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 class (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 委托一起使用,但似乎我错过了一些代码行,无论它是什么我无法理解我在做什么如何引发事件 asynchronously 错误?这是我的 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);
  }
}
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 控件。

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

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

您应该始终关闭处置 您的连接。方法的名称看起来 "scary" 但实际上连接被重用。请阅读此 MSDN article 以获取更多详细信息。

您提出的代码看起来过于复杂。我认为您应该考虑使用 async/await pattern which is in general not multithreaded, but it handles UI responsiveness issues and simplifies writing/reading of the code. In newer versions of .NET almost all methods that are potentially long to execute has async versions. So for example your data access layer might look like that (I'm using 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 将在 UI 线程中执行,因为 TaskScheduler.FromCurrentSynchronizationContext().

使用这个选项

http://www.asp.net/mvc/overview/older-versions-1/models-(data)/creating-model-classes-with-the-entity-framework-cs

使用简单,数据库操作简单,代码少。