Я разработал часть доступа к данным нашей платформы так, чтобы каждый раз бизнес-объект (BO) взаимодействовал с базой данных, это должно было бы открыть соединение, вызвать уровень доступа к данным (чтобы выполнить запрос) и затем закрыть соединение. Затем, если бы это должно было работать в транзакции, то это открыло бы соединение, начало бы транзакцию, вызвать уровень доступа к данным (чтобы выполнить запрос) и затем фиксировать транзакцию, заключить сделку и наконец закрыть соединение.
Я сделал это этот путь в мышлении "открытого поздний, закрываюсь рано" …, но что, если я должен был назвать другой BOS для представления данных в единственной транзакции? Существует ли лучший способ обработать открытие и заключительные соединения, а также работу с транзакциями?
Я - новобранец в разработке архитектуры приложения, таким образом, я надеюсь, что не делаю, это неправильно … любая справка ценится.
Если данный бизнес-объект должен выполнять различные методы в транзакции, используйте TransactionScope
следующим образом:
using ( var transactionScope = new TransactionScope() )
{
this.Save();
childObjA.Save();
childObjB.Save();
childObjC.Save();
childObjD.Save();
transactionScope.Complete();
}
Если какой-либо из объектов вызывает исключение, он откатит транзакцию.
См. справочную страницу MSDN для TransactionScope
для получения дополнительной информации.
Когда абстракции более высокого уровня зависят от абстракций более низкого уровня (таких как классы бизнес-логики, зависящие от соединений данных), обычно абстракции более низкого уровня передаются через конструктор. Эта техника называется инъекцией конструктора :
public class OrderService
{
private SqlConnection connection;
public OrderService(SqlConnection connection)
{
if (connection == null)
throw new ArgumentNullException("connection");
this.connection = connection;
}
// Other methods
}
Затем это позволяет вам писать код для служб, подобный следующему:
using (TransactionScope tsc = new TransactionScope())
using (SqlConnection connection = new SqlConnection(...))
{
connection.Open();
OrderService os = new OrderService(connection);
os.ProcessOrder(myOrder);
ShippingService ss = new ShippingService(connection);
ss.ShipOrder(myOrder);
tsc.Complete();
}
Что, скорее всего, будет тем, что вы хотите, в конце концов - возможность совместного использования одного соединения между множеством сервисов.
Это также помогает отделить ваши службы от деталей реализации соединения для передачи данных. Таким образом, если вы хотите сделать что-то вроде изменения настроек подключения при определенных обстоятельствах, вам не нужно копаться в деталях 50 различных служб, вам нужно только изменить одну строку кода, которая создает соединение.
И еще кое-что: если вы собираетесь использовать TransactionScope
, обязательно добавьте Transaction Binding = Explicit Unbind
в строку подключения, иначе это действительно может закончиться с противоречивыми данными, если время транзакции истекло.
Похоже, вы правильно поняли. Если необходимо задействовать несколько БО, то один из них должен быть «контроллером» - он должен открывать и закрывать соединение и передавать его другим. Или какой-нибудь объект-оболочка может обработать соединение и передать его каждому из BO. Возможно, ваши BO должны быть спроектированы так, чтобы работать как самостоятельно (обрабатывать собственное соединение), так и принимать существующее соединение извне.
Вероятно, вы ищете шаблон Единица работы и шаблон Реестр . Эти два шаблона могут работать совместно, чтобы разделить проблемы поиска бизнес-объектов и их отслеживания для последующей фиксации в хранилище данных в виде транзакции.
Я бы также изучил объектно-реляционное отображение или ORM. ORM - это композиция более высокого уровня, состоящая из единицы работы, реестра, игнорирования персистентности и других шаблонов, которые обеспечивают очень четкое разделение вашей бизнес-логики от вашей логики сохраняемости. Используя ORM, вы, как правило, можете избавиться от необходимости писать хранимые процедуры, создавать собственный DAL и т. Д. ORM позаботится о ваших проблемах с постоянством, позволяя вам сосредоточиться на бизнес-логике, которую необходимо выполнить.
Поскольку вы используете C # и .NET, я бы посмотрел на Entity Framework (v4, не используйте v1) или LINQ to SQL. Оба являются сопоставителями ИЛИ, которые поставляются с .NET framework начиная с v3.5 и выше. LINQ to SQL - это очень простая и хорошо продуманная ORM, которая должна помочь вам очень быстро. Entity Framework - это гораздо более богатая ORM, которая также очень хорошо оборудована (лучше, чем LINQ to SQL) и предлагает значительно больше функциональных возможностей. Существуют также сторонние ORM, которые могут выполнять эту работу, в том числе бесплатная под названием NHibernate. Хотя NHibernate не так хорошо проработан, как ORM от Microsoft, это очень зрелая ORM с открытым исходным кодом, пользующаяся большим успехом в сообществе.
Если ORM невозможен, я бы посмотрел в Единицу работы , Реестр (или Хранилище), Невежество настойчивости, Разделение проблем , Single Responsibility и другие связанные шаблоны.
Как упоминалось другими, TransactionScope
- это правильный путь.
Если вы используете SQL Server 2008 и .NET 3.5, я бы изменил дизайн, чтобы бизнес-объект управлял транзакцией, и оставил открытие и закрытие соединения с уровнем данных.
При включенном пуле соединений вы фактически не будете нести накладные расходы на открытие физического соединения с базой данных, и ваши соединения будут открыты только при выполнении реальной работы. Поскольку (я предполагал) у вас есть SQL Server 2008 с .NET 3.5, ваша транзакция не перерастет в распределенную транзакцию (если вы не откроете несколько соединений одновременно), поэтому вы получите лучшее из обоих миров.
Затем вы можете написать свой бизнес-объект следующим образом:
using (TransactionScope transactionScope = new TransactionScope())
{
DataObject dataObject = new DataObject();
dataObject.UpdateQuantity(...);
ShippingManager shippingManager = new ShippingManager();
shippingManager.ShipOrder(...);
transactionScope.Complete()
}
Это избавит от необходимости передавать строки подключения всем бизнес-объектам и упростит координацию транзакций.
Обновление
Прелесть System.Transactions в том, что все транзакции управляются за вас независимо от того, какое соединение вы используете. Вы просто объявляете TransactionScope, и весь доступ к базе данных в этом TransactionScope будет происходить в рамках одной транзакции (если вы не запросите иное с другими настройками TransactionScope).
В прошлом (SQL Server 2005 .NET 2.0), если вы открывали и закрывали соединение, а затем открывали и закрывали другое соединение (даже с той же строкой соединения), транзакция переводилась из облегченной транзакции в распределенную. .Это было нежелательно, потому что страдает производительность (связь с MSDTC не выполняется, а протокол двухфазной фиксации), и MSDTC может быть сложной задачей для настройки во многих производственных средах (брандмауэры и безопасность).
В SQL Server 2008 и .NET 3.5 они добавили возможность избежать этого повышения при открытии и закрытии нескольких соединений с одной и той же строкой соединения в рамках одной транзакции. Для действительно хорошего объяснения того, что они видели Расширение облегченных транзакций в SqlClient .
Обновление 2
Транзакции с Oracle 10g будут правильно работать с TransactionScope. И похоже, что ODP.NET поддерживает облегченные транзакции (что приятно). К сожалению, я думаю, что переход к распределенной транзакции будет происходить при закрытии и открытии соединений.
Если вы хотите избежать распределенной транзакции, вы можете передать соединение каждому вызову метода / бизнес-объекту. Если вы не хотите передавать соединение, вы можете использовать класс ConnectionScope , который сохраняет соединение открытым в потоке. Альтернативой этому могло бы быть использование блока приложения доступа к данным Enterprise Library 3.0 (и выше). Блок доступа к данным может обнаруживать, что транзакция выполняется, и использовать то же соединение , чтобы избежать распределенной транзакции.