C#中的嵌套连接

时间:2016-02-22 04:16:09

标签: c# asp.net asp.net-mvc

我最近在C#中创建了一个轻量级ORM工具并将其发布在github上。 https://www.github.com/RiceRiceBaby/ADOCRUD。我的ORM所做的一件事就是为您管理连接的打开和关闭。我这样做的方法是在上下文类的构造函数中打开连接并在dispose上关闭它。下面的代码是如何使用我的工具的示例。

using (ADOCRUDContext context = new ADOCRUDContext(connectionString)
{
     context.Insert<Product>(p);
     context.Commit();
}

我实现这种自动连接管理的问题是它可以防止嵌套连接工作。例如,以下内容不起作用:

public Product GetProductById(int productId)
{
  Product p = null;

  using (ADOCRUDContext context = new ADOCRUDContext(connectionString))
  {
    p = context.QueryItems<Product>("select * from dbo.Product where Id = @id", new { id = productId }).FirstOrDefault();
  }

  return p;
}

public void UpdateProduct(int productId)
{
  using (ADOCRUDContext context = new ADOCRUDContext(connectionString))
  {
    Product p = this.GetProductById(productId);
    p.Name = "Basketball";
    context.Update<Product>(p);
    context.Commit();
  }
}

这不起作用的原因是因为方法GetProductId方法的using语句中的最后一个大括号将关闭GetProductId上下文和UpdateProduct上下文的sql连接。这意味着context.Update<Product>(p);将抛出异常,因为连接已经关闭。

我认为这种连接的自动管理是一个好主意,但我担心如果他们没有能力拥有嵌套连接,人们可能不会使用我的工具。有没有办法保持连接管理,同时允许嵌套连接,如上面的代码?

3 个答案:

答案 0 :(得分:0)

您可以使用引用计数来跟踪代码通过的语句数量,并在正确的时间正确关闭上下文,然后在块的外部使用单个using语句多个数据库调用将强制使用相同的上下文实例。

null

您可以在CSLA DbContextManager Class中看到引用计数的示例。

但是,许多现代应用程序使用依赖注入容器来控制数据库上下文的生命周期。因此,如果有一种方法可以作为引用计数的替代方法(实际上,如果它被注入到使用它的类的构造函数中,那么当前的ADOCRUDContext可能会有效),这是有意义的,但它可能有意义将连接字符串包装到另一个对象中,这样就可以在没有显式配置的情况下注入它,并且更容易交换。)

此外,许多人认为必须新建你的ADOCRUDContext inline是反模式,因为没有办法模拟上下文(因此,无法测试它)。解决这个问题的方法是使用abstract factory pattern来允许将上下文注入到类中。

答案 1 :(得分:0)

我很惊讶没有人说这个问题的第一次分析是错误的。

我对你的所有答案都是正确的

  • 多次使用会导致错误,例如
  • 使用分离的连接
  • 使用工厂

好的,但这段代码的第一个问题只是对象

Product p = this.GetProductById(productId);

将丢失其上下文,并且第一次使用想要使用断开连接的对象并且被禁止。但是,如果您将此对象附加到打开的第一个上下文,则可以重复使用它并执行更新

总结一下,您有这些步骤

  1. 使用,打开第一个上下文
  2. 致电GetProductById
  3. 使用第二个上下文
  4. 在第二个环境中获取产品
  5. 关闭并配置第二个上下文
  6. 更新第一个上下文中返回的产品的属性
  7. 在第一个上下文中更新产品
  8. 错误:已退回的产品已断开连接
  9. 现在你必须

    1. 使用,打开第一个上下文
    2. 致电GetProductById
    3. 使用第二个上下文
    4. 在第二个环境中获取产品
    5. 关闭并配置第二个上下文
    6. 更新第一个上下文中返回的产品的属性
    7. ATTACH产品对象到第一个上下文
    8. 在第一个上下文中更新产品
    9. 确定产品已更新
    10. 尝试使用此代码附加

      var entry = context.Entry(entity);
      if (entry != null)
      {
           entry.State = EntityState.Modified;
      }
      

      修改

      就像我们在评论中讨论的那样,问题来自ADOCRUDContext

      的源代码

      成员sqlConnection和sqlTransaction标记为 static ,不应该。

      当我写这些行时,进行了更正。它只是标记为其他人的解决方案

答案 2 :(得分:0)

不,不要创建一些魔术,它允许你的示例代码在多个using语句中运行,因为这是不期望的。创建using语句只是为了能够控制对象的清理/生命周期。

如果您绕过它,则会取消用户的控制权。

最理智的方法是简单地在类构造函数中传递上下文(或者只是在构造函数中创建上下文)。通过这样做,所有方法都将共享相同的上下文。

另一种方法是做TransactionScope之类的事情。即能够共享内部范围,但也能够明确告知是否需要新范围。

public Product GetProductById(int productId)
{
  Product p = null;

  using (ADOCRUDContext context = new ADOCRUDContext(connectionString))
  {
    p = context.QueryItems<Product>("select * from dbo.Product where Id = @id", new { id = productId }).FirstOrDefault();
  }

  return p;
}

public void UpdateProduct(int productId)
{
  //see the second argument
  using (ADOCRUDContext context = new ADOCRUDContext(connectionString, Options.ReuseExisting))
  {
    Product p = this.GetProductById(productId);
    p.Name = "Basketball";
    context.Update<Product>(p);
    context.Commit();
  }
}

但是,这需要您处理[ThreadStatic]中跟踪当前打开的上下文的ADOCRUDContext变量。请注意[ThreadStatic]与TPL / Tasks不能很好地协同工作。因此,这种方法要复杂得多,因为您需要了解不同的线程技术在.NET中的工作原理。