Недавно я создал легкий инструмент ORM на C # и разместил его на github. https://www.github.com/RiceRiceBaby/ADOCRUD. Одна из функций моего ORM - это управление открытием и закрытием соединений за вас. Я делаю это так: открываю соединение в конструкторе моего класса контекста и закрываю его при удалении. Код ниже - это пример того, как использовать мой инструмент.

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

Причина, по которой это не сработает, заключается в том, что последняя скобка в операторе using метода GetProductId закроет соединения sql как для контекста GetProductId, так и для контекста UpdateProduct. Это означает, что context.Update<Product>(p); вызовет исключение, потому что соединение уже было закрыто.

Я думал, что это автоматическое управление соединениями - хорошая идея, но боюсь, что люди могут не использовать мой инструмент, если у них нет возможности иметь вложенные соединения. Есть ли способ сохранить это управление соединениями, разрешив вложенные соединения, как в приведенном выше коде?

1
RiceRiceBaby 22 Фев 2016 в 07:16

2 ответа

Вы можете использовать подсчет ссылок , чтобы отслеживать, сколько операторов using проходит через код, и правильно закрывать контекст в нужное время, а затем один оператор using вне блока с несколькими базами данных. вызовы заставят использовать один и тот же экземпляр контекста.

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

Вы можете увидеть пример подсчета ссылок в Класс CSLA DbContextManager.

Однако многие современные приложения используют контейнер внедрения зависимостей для управления временем жизни контекста БД. Поэтому было бы разумно, если бы существовал способ сделать это таким образом в качестве альтернативы подсчету ссылок (на самом деле, ваш текущий ADOCRUDContext, вероятно, работал бы, если бы он был введен в конструктор классов, которые его используют, но может иметь смысл оберните строку подключения в другой объект, чтобы ее можно было внедрить без явной настройки и упростить замену).

Кроме того, многие будут утверждать, что необходимость обновлять встроенный ADOCRUDContext является анти-шаблоном, потому что нет способа имитировать контекст (следовательно, нет способа его проверить). Для решения этой проблемы можно использовать абстрактный шаблон фабрики. чтобы разрешить внедрение контекста в класс.

0
NightOwl888 22 Фев 2016 в 17:13

Нет, не создавайте магии, которая позволила бы вашему примеру кода работать с несколькими операторами 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.

0
jgauffin 22 Фев 2016 в 10:25