【发布时间】:2009-01-29 22:01:20
【问题描述】:
您将如何从业务逻辑层中的一个方法调用数据访问层中的多个方法,以便所有 SQL 命令都存在于一个 SQL 事务中?
每个 DAL 方法都可以从 BLL 中的其他位置单独调用,因此不能保证数据层方法始终是事务的一部分。我们需要这个功能,所以如果数据库在长时间运行的过程中脱机,就没有提交。业务层根据之前每次调用的结果编排不同的数据层方法调用。我们只想在整个过程的最后提交(从业务层)。
【问题讨论】:
您将如何从业务逻辑层中的一个方法调用数据访问层中的多个方法,以便所有 SQL 命令都存在于一个 SQL 事务中?
每个 DAL 方法都可以从 BLL 中的其他位置单独调用,因此不能保证数据层方法始终是事务的一部分。我们需要这个功能,所以如果数据库在长时间运行的过程中脱机,就没有提交。业务层根据之前每次调用的结果编排不同的数据层方法调用。我们只想在整个过程的最后提交(从业务层)。
【问题讨论】:
首先,您必须遵守您在 BLL 中指定为单个方法的原子工作单元。这将(例如)创建客户、订单和订单项目。然后,您可以将这一切整齐地包装在 TransactionScope using 语句中。 TransactionScope 是这里的秘密武器。下面是一些幸运的是我现在正在处理的代码:):
public static int InsertArtist(Artist artist)
{
if (artist == null)
throw new ArgumentNullException("artist");
int artistid = 0;
using (TransactionScope scope = new TransactionScope())
{
// insert the master Artist
/*
we plug the artistid variable into
any child instance where ArtistID is required
*/
artistid = SiteProvider.Artist.InsertArtist(new ArtistDetails(
0,
artist.BandName,
artist.DateAdded));
// insert the child ArtistArtistGenre
artist.ArtistArtistGenres.ForEach(item =>
{
var artistartistgenre = new ArtistArtistGenreDetails(
0,
artistid,
item.ArtistGenreID);
SiteProvider.Artist.InsertArtistArtistGenre(artistartistgenre);
});
// insert the child ArtistLink
artist.ArtistLinks.ForEach(item =>
{
var artistlink = new ArtistLinkDetails(
0,
artistid,
item.LinkURL);
SiteProvider.Artist.InsertArtistLink(artistlink);
});
// insert the child ArtistProfile
artist.ArtistProfiles.ForEach(item =>
{
var artistprofile = new ArtistProfileDetails(
0,
artistid,
item.Profile);
SiteProvider.Artist.InsertArtistProfile(artistprofile);
});
// insert the child FestivalArtist
artist.FestivalArtists.ForEach(item =>
{
var festivalartist = new FestivalArtistDetails(
0,
item.FestivalID,
artistid,
item.AvailableFromDate,
item.AvailableToDate,
item.DateAdded);
SiteProvider.Festival.InsertFestivalArtist(festivalartist);
});
BizObject.PurgeCacheItems(String.Format(ARTISTARTISTGENRE_ALL_KEY, String.Empty, String.Empty));
BizObject.PurgeCacheItems(String.Format(ARTISTLINK_ALL_KEY, String.Empty, String.Empty));
BizObject.PurgeCacheItems(String.Format(ARTISTPROFILE_ALL_KEY, String.Empty, String.Empty));
BizObject.PurgeCacheItems(String.Format(FESTIVALARTIST_ALL_KEY, String.Empty, String.Empty));
BizObject.PurgeCacheItems(String.Format(ARTIST_ALL_KEY, String.Empty, String.Empty));
// commit the entire transaction - all or nothing
scope.Complete();
}
return artistid;
}
希望你能明白要点。基本上,这是一个成功或失败的工作,无论任何不同的数据库(即在上面的示例中,艺术家和艺术家艺术家类型可以托管在两个单独的数据库存储中,但 TransactionScope 不太关心这一点,它在 COM+ 级别工作并管理原子性它可以“看到”的范围)
希望对你有帮助
编辑:您可能会发现 TransactionScope 的初始调用(在应用程序启动时)可能会稍微引人注目(即在上面的示例中,如果第一次调用,可能需要2-3 秒完成),但是,后续调用几乎是瞬时的(即通常为 250-750 毫秒)。简单的接触点交易与(笨拙的)替代方案之间的权衡减轻了(对我和我的客户而言)初始“加载”延迟。
只是想证明轻松并非没有妥协(尽管是在初始阶段)
【讨论】:
您所描述的是长事务的“定义”。
每个 DAL 方法都可以简单地提供操作(无需任何特定提交)。您的 BLL(实际上是在您协调对 DAL 的任何调用的地方)是您可以选择提交或执行“保存点”的地方。保存点是一个可选项目,您可以使用它来允许在长时间运行的事务中“回滚”。
因此,例如,如果我的 DAL 的方法 DAL1、DAL2、DAL3 都是可变的,它们将简单地“执行”数据更改操作(即某种类型的创建、更新、删除)。从我的 BLL 中,假设我有 BL1 和 BL2 方法(BL1 长期运行)。 BL1 调用所有上述 DAL 方法(即 DAL1...DAL3),而 BL2 仅调用 DAL3。
因此,在执行每个业务逻辑方法时,您可能会遇到以下情况:
BL1(长期交易)-> {savepoint} DAL1 -> {savepoint} DAL2 -> DAL3 {commit/end}
BL2 -> DAL3 {提交/结束}
“保存点”背后的想法是,如果数据操作出现问题,它可以允许 BL1 随时回滚。仅当所有三个操作都成功完成时,才会提交长事务。 BL2 仍然可以调用 DAL 中的任何方法,并且它负责控制提交。注意:您也可以在短期/常规交易中使用“保存点”。
【讨论】:
好问题。这触及了阻抗失配的核心。
这是使用存储过程的最有力论据之一。原因:它们旨在将多个 SQL 语句封装在一个事务中。
同样可以在 DAL 中按程序完成,但会导致代码不太清晰,同时通常会导致耦合/内聚平衡向错误的方向移动。
出于这个原因,我在比简单封装表更高的抽象层次上实现 DAL。
【讨论】:
以防我在原始文章中的评论没有“坚持”,这是我作为附加信息添加的内容:
【讨论】: