简体   繁体   English

C#中的嵌套连接

[英]Nested Connections in C#

I recently created a light weight ORM tool in C# and posted it on github. 我最近在C#中创建了一个轻量级的ORM工具,并将其发布在github上。 https://www.github.com/RiceRiceBaby/ADOCRUD . https://www.github.com/RiceRiceBaby/ADOCRUD One of the things my ORM does is manage the opening and closing of connections for you. 我的ORM要做的一件事是为您管理连接的打开和关闭。 The way I do this is I open the connection in the constructor of my context class and close it on dispose. 我这样做的方法是在上下文类的构造函数中打开连接,并在处理时将其关闭。 The code below is an example of how to use my tool. 下面的代码是如何使用我的工具的示例。

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

The problem with me implementing this automatic management of connection is that it prevents nested connections from working. 我实现这种自动连接管理的问题是,它阻止了嵌套连接的工作。 For example, the following will not work: 例如,以下内容不起作用:

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

The reason this won't work is because the last brace in the using statement of the method GetProductId method will close the sql connections for both GetProductId context and UpdateProduct context. 之所以不起作用,是因为方法GetProductId方法的using语句中的最后一个括号将关闭GetProductId上下文和UpdateProduct上下文的sql连接。 This means that context.Update<Product>(p); 这意味着context.Update<Product>(p); will throw an exception because the connection has already been closed. 由于连接已关闭,将引发异常。

I thought this automatic management of connections is a good idea but I'm afraid people might not use my tool if they don't have the ability to have nested connections. 我认为这种自动进行连接管理是个好主意,但是如果人们没有嵌套连接的能力,恐怕他们可能不会使用我的工具。 Is there a way to keep this management of connections while allowing nested connections like the code above? 有没有办法在允许嵌套连接(如上面的代码)的同时保持对连接的管理?

You could use reference counting to keep track of how many using statements the code goes through and to properly close the context at the right time, and then a single using statement on the outside of a block with multiple database calls will force the use of the same context instance. 您可以使用引用计数来跟踪代码经过了多少个using语句并在正确的时间正确关闭上下文,然后在具有多个数据库调用的块外部使用单个using语句将强制使用同一上下文实例。

using (var context = this.contextManager.GetContext(connectionString))
{
    var product = DL.GetProductById(id);
    product.name = "The Best Product";
    DL.UpdateProduct(product);
}

You can see an example of reference counting in the CSLA DbContextManager Class . 您可以在CSLA DbContextManager Class中看到引用计数的示例。

However, many modern applications use a dependency injection container to control the lifetime of the DB Context. 但是,许多现代应用程序都使用依赖项注入容器来控制数据库上下文的生存期。 So it would make sense if there were a way to do it that way as an alternative to reference counting (actually, your current ADOCRUDContext would probably work if it were injected into the constructor of the classes that use it, but it might make sense to wrap the connection string into another object so it can be injected without explicit configuration, and swapped more easily). 因此,如果有一种方法可以代替引用计数,那将是有道理的(实际上,如果将当前的ADOCRUDContext注入使用它的类的构造函数中,则可能会起作用,但对于将连接字符串包装到另一个对象中,以便无需显式配置即可将其插入,并且更容易交换)。

Also, many would argue that having to new up your ADOCRUDContext inline is anti-pattern because there is no way to mock the context (hence, no way to test it). 而且,许多人认为必须重新内联ADOCRUDContext内联是反模式的,因为没有方法可以模拟上下文(因此无法测试它)。 A way around that would be to use an abstract factory pattern to allow injecting the context into the class. 一种解决方法是使用抽象工厂模式来允许将上下文注入到类中。

I am surprised that nobody said that the first analysis of the issue is wrong. 令我惊讶的是,没有人说对此问题的首次分析是错误的。

I am right to all of your answer 我对你的所有回答都是正确的

  • multiple using will cause bugs like in this case 多次使用将导致这种情况下的错误
  • use separated connections 使用分开的连接
  • use factory 使用工厂

Ok ok but the first issue of this code is just the object 好的,但是此代码的第一个问题只是对象

Product p = this.GetProductById(productId);

will lose its context and the first using want to use a disconnected object and that is forbidden. 将会失去上下文,并且第一个使用者想使用断开连接的对象,这是被禁止的。 But if you attached this object to the first context opened you can reuse it and do your Update . 但是,如果将此对象附加到打开的第一个上下文,则可以重用它并进行Update

To summarize, you have these steps 总结一下,您有以下步骤

  1. USING, open 1st context 使用,打开第一个上下文
  2. call to GetProductById 调用GetProductById
  3. USING 2nd context 使用第二上下文
  4. Get the product in the 2nd context 在第二种情况下获取产品
  5. Close and Dispose 2nd context 关闭并处理第二个上下文
  6. Update properties of Product returned in the 1st context 更新在第一个上下文中返回的产品的属性
  7. Update product in the 1st context 在第一种情况下更新产品
  8. ERROR: the product returned is disconnected 错误:退回的产品已断开连接

Now you must have 现在你必须有

  1. USING, open 1st context 使用,打开第一个上下文
  2. call to GetProductById 调用GetProductById
  3. USING 2nd context 使用第二上下文
  4. Get the product in the 2nd context 在第二种情况下获取产品
  5. Close and Dispose 2nd context 关闭并处理第二个上下文
  6. Update properties of Product returned in the 1st context 更新在第一个上下文中返回的产品的属性
  7. ATTACH Product object to the first context 将产品对象附加到第一个上下文
  8. Update product in the 1st context 在第一种情况下更新产品
  9. OK The Product is updated 确定产品已更新

Try this code to attach 尝试附加此代码

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

EDIT 编辑

Like we discuss in comments, the issue comes from the source code of ADOCRUDContext 就像我们在评论中讨论的那样,问题来自ADOCRUDContext的源代码。

The member sqlConnection and sqlTransaction are marked static and should not. 成员sqlConnection和sqlTransaction标记为静态 ,不应标记为静态

Correction was done when I wrote these lines. 当我写这些行时,校正就完成了。 It is just to marked as solution for others 只是标记为其他人的解决方案

No, do not create some magic which would allow your example code to function over multiple using statements as that's not expected. 不,不要创造任何魔力,因为这不会使您的示例代码在多个using语句上起作用。 A using statement is created just to be able to control the cleanup/lifetime of the object. 创建using语句只是为了能够控制对象的清除/生存期。

If you bypass that, you take way the control from your user. 如果您绕过了这一点,则会从用户那里夺走控制权。

The most sane approach is to simply pass the context in the class constructor (or simply create the context in the constructor). 最理智的方法是简单地在类构造函数中传递上下文(或简单地在构造函数中创建上下文)。 By doing so, all methods would share the same context. 这样,所有方法将共享相同的上下文。

The other approach is to do something like TransactionScope . 另一种方法是执行类似TransactionScope ie be able to share an internal scope but also be able to explicitly tell if a new scope is required. 即能够共享一个内部范围,但也能够显式地确定是否需要新的范围。

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

However, that requires you to deal with [ThreadStatic] variable within your ADOCRUDContext that keeps track of the currently open context. 但是,这要求您在ADOCRUDContext中处理[ThreadStatic]变量,以跟踪当前打开的上下文。 Do note that [ThreadStatic] do not work very well with TPL/Tasks. 请注意, [ThreadStatic]在TPL /任务中不能很好地工作。 Thus this approach is much more complex since you need to be aware of how different threading techniques work i .NET. 因此,由于您需要了解.NET中不同的线程技术如何工作,因此该方法要复杂得多。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM