1

我对 Web 应用程序中的实体代码有一些 linq。它基本上记录了一个应用程序被下载了多少次。我担心这可能会发生:

  • 会话 1 读取下载计数(例如 50)
  • 会话 2 读取下载计数(再次,50)
  • 会话 1 将其递增并将其写入数据库(数据库存储 51)
  • 会话 2 将其递增并将其写入数据库(数据库存储 51)

这是我的代码:

private void IncreaseHitCountDB()
{
    JTF.JTFContainer jtfdb = new JTF.JTFContainer();

    var app =
        (from a in jtfdb.Apps
         where a.Name.Equals(this.Title)
         select a).FirstOrDefault();

    if (app == null)
    {
        app = new JTF.App();
        app.Name = this.Title;
        app.DownloadCount = 1;

        jtfdb.AddToApps(app);
    }
    else
    {
        app.DownloadCount = app.DownloadCount + 1;
    }

    jtfdb.SaveChanges();
}

这有可能发生吗?我怎么能阻止它?

谢谢你,菲德尔

4

3 回答 3

2

默认情况下,实体框架使用乐观并发模型。谷歌表示乐观意味着“对未来充满希望和信心”,而这正是 Entity Framework 的行为方式。也就是说,当您称它为SaveChanges()充满希望和自信”时,不会发生并发问题,因此它只是尝试保存您的更改。

Entity Framework 可以使用的另一种模型应该称为悲观并发模型(“期望最坏的结果”)。您可以逐个实体启用此模式。在您的情况下,您将在App实体上启用它。这就是我所做的:

步骤 1. 对实体启用并发检查

  1. 右键单击.edmx文件并选择打开方式...
  2. 在弹出对话框中选择XML (Text) Editor ,然后单击 OK。
  3. 在ConceptualModels中找到 App 实体。我建议切换大纲并根据需要扩展标签。你正在寻找这样的东西:

    <edmx:Edmx Version="2.0" xmlns:edmx="http://schemas.microsoft.com/ado/2008/10/edmx">
      <!-- EF Runtime content -->
      <edmx:Runtime>
        <!-- SSDL content -->
        ...
        <!-- CSDL content -->
        <edmx:ConceptualModels>
          <Schema Namespace="YourModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
            <EntityType Name="App">
    
  4. 在 EntityType 下,您应该会看到一堆<Property>标签。如果存在Name="Status"通过添加修改它ConcurrencyMode="Fixed"。如果该属性不存在,请将其复制到:

    <Property Name="Status" Type="Byte" Nullable="false" ConcurrencyMode="Fixed" />
    
  5. 保存文件并双击.edmx文件以返回设计器视图。

Step 2. 调用时的并发处理SaveChanges()

SaveChanges()将抛出两个异常之一。熟悉的UpdateExceptionOptimisticConcurrencyException

如果您对已ConcurrencyMode="Fixed"设置的实体进行了更改,实体框架将首先检查数据存储是否对其进行了任何更改。如果有更改,OptimisticConcurrencyException将抛出 a。如果没有进行任何更改,它将正常继续。

当您捕获时,OptimisticConcurrencyException您需要调用Refresh()方法ObjectContext并重做计算,然后再重试。Refresh()更新实体的调用RefreshMode.StoreWins意味着将使用数据存储中的数据解决冲突。同时更改的 DownloadCount 是一个冲突。

这就是我要让你的代码看起来像的样子。请注意,当您在获取 Entity 和调用 之间有很多操作时,这会更有用SaveChanges()

    private void IncreaseHitCountDB()
    {
        JTF.JTFContainer jtfdb = new JTF.JTFContainer();

        var app =
            (from a in jtfdb.Apps
             where a.Name.Equals(this.Title)
             select a).FirstOrDefault();

        if (app == null)
        {
            app = new JTF.App();
            app.Name = this.Title;
            app.DownloadCount = 1;

            jtfdb.AddToApps(app);
        }
        else
        {
            app.DownloadCount = app.DownloadCount + 1;
        }

        try
        {
            try
            {
                jtfdb.SaveChanges();
            }
            catch (OptimisticConcurrencyException)
            {
                jtfdb.Refresh(RefreshMode.StoreWins, app);
                app.DownloadCount = app.DownloadCount + 1;
                jtfdb.SaveChanges();
            }
        }
        catch (UpdateException uex)
        {
            // Something else went wrong...
        }
    }
于 2012-05-11T02:58:35.603 回答
1

如果您仅在即将增加下载计数列之前查询下载计数列,则可以防止这种情况发生,读取和增加之间花费的时间越长,另一个会话必须读取它的时间越长(以及稍后重写 - 错误地 - 增加number ) 从而弄乱了计数。

使用单个 SQL 查询:

UPDATE Data SET Counter = (Counter+1)

因为它的 Linq To Entities,这意味着延迟执行,对于另一个会话来搞砸计数(增加相同的基数,在那里失去 1 个计数)它必须尝试增加应用程序。我相信在两行之间下载计数:

    else
    {
        app.DownloadCount += 1; //First line
    }

    jtfdb.SaveChanges();  //Second line
}

这意味着发生更改的窗口(从而使先前的计数变旧)是如此之小,以至于对于这样的应用程序来说几乎是不可能的。

因为我不是 LINQ 专业人士,所以我不知道 LINQ 在添加一个之前是否真的获得了 app.DownLoadCount,或者只是通过一些 SQL 命令添加了一个,但在任何一种情况下,你都不应该担心那个恕我直言

于 2011-09-06T11:46:17.977 回答
0

您可以轻松测试在这种情况下会发生什么 - 启动一个线程,使其休眠,然后再启动另一个。

else
{
    app.DownloadCount = app.DownloadCount + 1;
}

System.Threading.Thread.Sleep(10000);
jtfdb.SaveChanges();

但简单的答案是不,Entity Framework默认执行任何并发检查(MSDN - Saving Changes andManaging Concurrency)

该网站将为您提供一些背景信息。

你的选择是

  • 启用并发检查,这意味着如果两个用户同时下载并且第一个更新在第二个读取之后但在第二个更新之前,您将收到异常。
  • 创建一个存储过程,它将直接增加表中的值,并在单个操作中从代码中调用存储过程 - 例如IncrementDownloadCounter。这将确保没有“读取”,因此没有“脏读取”的可能性。
于 2011-09-06T13:51:28.317 回答