[Orcas] 14. LINQ to SQL - Optimistic Concurrency

[ 2007-08-20 15:28:10 | 作者: yuhen ]
字号: | |
Optimistic Concurrency (乐观并发?) 是数据库设计中的一种常用手段,通过在更新前检查当前数据库记录是否发生改变来确保数据更新的有效性。通常做法是添加 TimeSpan 标记或直接比较列值。DLinq 缺省情况下会自动进行 Optimistic Concurrency 检查,我们用一个例子演示一下。

准备数据库,并创建 dbml。

uploads/200708/20_152916_1.gif


首先,我们看看 DLinq 的 Update 执行方式。
using (DataClasses1DataContext context = new DataClasses1DataContext(connStr))
{
    context.Log = Console.Out;

    var user = context.Users.Where(u => u.Name == "user1").First();
    user.Age += 1;

    Console.WriteLine("-----------------");
    context.SubmitChanges();
}

输出:
UPDATE [dbo].[User]
SET [Age] = @p3
WHERE ([Name] = @p0) AND ([Id] = @p1) AND ([Age] = @p2)

SELECT [t1].[Id]
FROM [dbo].[User] AS [t1]
WHERE ((@@ROWCOUNT) > 0) AND ([t1].[Name] = @p4)

-- @p0: Input String (Size = 5; Prec = 0; Scale = 0) [User1]
-- @p1: Input Int32 (Size = 0; Prec = 0; Scale = 0) [1]
-- @p2: Input Int32 (Size = 0; Prec = 0; Scale = 0) [1]
-- @p3: Input Int32 (Size = 0; Prec = 0; Scale = 0) [2]
-- @p4: Input String (Size = 5; Prec = 0; Scale = 0) [User1]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.20706.1

通过输出的 SQL 语句,我们发现 DLinq 更新条件是检查所有字段值。接下来,我们在 "context.SubmitChanges();" 代码行设置断点。当程序执行到该断点时,我们手工修改数据库中 user1 的 Age 值,这样在执行 SubmitChanges() 时就会导致数据库记录和 User OldValue 不符,从而触发并发冲突(concurrency conflicts)。

uploads/200708/20_152922_2.gif


要解决并发冲突,可以采取的方式包括:

1. UpdateCheck

修改指定列的 UpdateCheck 特性,使其不进行并发冲突检查。

uploads/200708/20_152927_3.gif

[Column(Storage = "_Age", DbType = "Int", UpdateCheck = UpdateCheck.Never)]
public System.Nullable<int> Age
{
    get
    {
        return this._Age;
    }
    set
    {
        if ((this._Age != value))
        {
            this.OnAgeChanging(value);
            this.SendPropertyChanging();
            this._Age = value;
            this.SendPropertyChanged("Age");
            this.OnAgeChanged();
        }
    }
}


在进行特性设置后,再进行并发冲突演示时,不再抛出异常。

2. ObjectChangeConflict

除了修改特性外,我们还可以通过捕获异常,通过 ObjectChangeConflict 对象来自定义并发冲突处理。这显然比第一种静态方式要更灵活得多。(注意将前面修改的 UpdateCheck 改回来。)
using (DataClasses1DataContext context = new DataClasses1DataContext(connStr))
{
    var users = from u in context.Users select u;

    foreach (var user in users)
    {
        Console.WriteLine("{0},{1}", user.Name, user.Age);
        user.Age += 1;
    }

    try
    {
        context.SubmitChanges(ConflictMode.ContinueOnConflict);
    }
    catch (ChangeConflictException ex)
    {
        foreach (var o in context.ChangeConflicts)
        {
            o.Resolve(RefreshMode.KeepChanges);
        }    
    }

    context.SubmitChanges(); // 注意!
}

ConflictMode 有两种选择:
  • FailOnFirstConflict: 在第一次并发冲突时立即触发异常。(缺省)
  • ContinueOnConflict: 发生并发冲突时,继续后续更新,直到所有更新完成后触发异常。
DataContext.ChangeConflicts 保存了所有冲突信息,我们可以通过 ObjectChangeConflict.Resolve() 方法来决定是否进行强制更新。

RefreshMode 枚举决定了更新以及实体对象刷新方式。
  • KeepChanges: 强制更新被修改的实体属性,同时使用数据库数据刷新其他实体属性。
  • KeepCurrentValues: 将当前所有实体属性值强制更新到数据库。(缺省)
  • OverwriteCurrentValues: 放弃更新,并使用数据库数据刷新当前实体。
[最后修改由 yuhen, 于 2007-10-20 09:46:33]
评论Feed 评论Feed: http://www.rainsts.net/feed.asp?q=comment&id=536

这篇日志没有评论。

发表评论
表情图标
[smile] [confused] [cool] [cry]
[eek] [angry] [wink] [sweat]
[lol] [stun] [razz] [redface]
[rolleyes] [sad] [yes] [no]
[heart] [star] [music] [idea]
UBB代码
转换链接
表情图标
悄悄话
用户名:   密码:  
验证码 * 请输入验证码