【问题标题】:Entity framework - increase number by 1实体框架 - 将数字增加 1
【发布时间】:2016-05-27 16:48:35
【问题描述】:

我在数据库中有一行来计算用户登录总数
我试图通过获取行并向其添加 +1 来增加数量
在我尝试过之后我不确定并发性,计数器增加了 1 而不是增加了 2,因为它“应该”(如果许多用户将同时登录)

using(var db = new Database()) {                                    
    db.Settings.FirstOrDefault(x => x.Name == "Logins").Counter++;
    using(var db2 = new Database()) {
        db2.Settings.FirstOrDefault(x => x.Name == "Logins").Counter++;
        db2.SaveChanges();
    }
    db.SaveChanges();
}

【问题讨论】:

  • 如果您使用 FirstOrDefault,它将始终给出计数 1,并且向其添加 1 将始终增加 1。
  • @AkashAmin 您还有其他建议吗?
  • 这取决于您表中的数据。
  • @AkashAmin 我想为管理面板存储一些全局“变量”或值,我想知道所有用户已经登录了多少,所以我在“登录”行中添加了 +1但是如果 10 个用户同时登录呢?并发和一些增加可能不像我的示例代码那样工作
  • 我会在这里做一个纯sql调用stackoverflow.com/questions/9293900/…

标签: asp.net sql-server entity-framework


【解决方案1】:

为什么不制作一个单独的表来存储已登录的人数,当有人成功登录时,该字段会增加,而当用户注销时,该字段会减少。例如登录:

  _Users = context.Users.First(aa => aa.UserName.ToUpper() == _UserName.ToUpper() && aa.MDesktop == true);
                    if (_Users != null)
                    {
                    context.LogEntry.FirstOrDefault().Counter++;
                    context.SaveChanges();
                    }

【讨论】:

  • 如果有 2 个用户同时登录呢?用户 1 获得了自己的上下文,总共 0 登录,用户 2 获得相同的登录 0,然后他们都做 0+1 并且值将是 1 而不是 2
  • 发生这种情况的可能性非常低,但如果这不起作用,您可以使用 session 来存储这些类型的变量。
【解决方案2】:

这是旧的,但它仍然是新 EF 开发人员的相关讨论,值得解释。

OP 的示例使用了两个不同的 DBContext,实际上 OP 定义了两个不同的工作单元,重要的是,它们都不知道另一个存在。

假设"Logins" 设置的当前值为5
出于本演练的目的,我们将从Settings 请求的两个实例保存到相关数据库上下文范围之外的变量中:

Setting setting1 = null;
Setting setting2 = null;

using(var db = new Database()) {  
    // DB: 5, Setting1: null, Setting2: null                                  

    // Load the value of setting1 from the database
    setting1 = db.Settings.FirstOrDefault(x => x.Name == "Logins");
    // DB: 5, Setting1: 5, Setting2: null

    // Increment the value of setting1
    setting1.Counter++;

    // at this point, no changes have been saved yet, the DB still holds the original value for "Logins"
    // DB: 5, Setting1: 6, Setting2: null

    // Create a new context called DB2
    using(var db2 = new Database()) {
        // load setting2 from the DB
        setting2 = db2.Settings.FirstOrDefault(x => x.Name == "Logins");
        // right now setting2 still has a value of 5, the previous change was not yet committed
        // DB: 5, Setting1: 6, Setting2: 5

        setting2.Counter++;
        // DB: 5, Setting1: 6, Setting2: 6
        
        // Save the value of Setting2 back to the database
        db2.SaveChanges();
        // DB: 6, Setting1: 6, Setting2: 6

        // At this point setting1, setting2, and the DB all agree the value is 6.
    }

    // The context is only aware that we previously set the value of setting1 to 6
    // so it issues an update to the DB
    db.SaveChanges();

    // ultimately this update would not actually change anything.
}

Entity FrameworkUnit of WorkRepository 数据访问模式都表现出这种行为,当您创建 new em> DbContext IRepositoryIUnitOfWork 这样做是为了隔离可能在同一时间点存在的任何其他上下文,在同一方法或不同线程或不同线程中实例化新上下文之间没有区别甚至在完全不同的服务器上执行。如果您需要实现计数器或增量值,当我们首先缓存字段的值,然后递增值,然后将该值写回数据库时,总是存在一定程度的不确定性。

为了最大程度地减少潜在冲突,请读取记录并在之后立即保存,然后通常在使用此设置之前重新查询它的值。

您可以在逻辑中多次调用.SaveChanges(),在此示例中,只需在实例化第二个上下文之前保存,或者至少在第二个上下文加载之前从数据库中加载记录就足以看到值增加了两次:

using(var db = new Database()) {                                    
    db.Settings.FirstOrDefault(x => x.Name == "Logins").Counter++;
    db.SaveChanges(); // save it back as soon as we've made the change
    using(var db2 = new Database()) {
        db2.Settings.FirstOrDefault(x => x.Name == "Logins").Counter++;
        db2.SaveChanges();
    }
    db.SaveChanges();
}

在可能的情况下,如果您可以避免需要递增或计数器字段的架构,您会发现代码更简单,而是可以将计数逻辑转换为基于查询的解决方案。

计数器当然是一种特殊情况,您始终可以直接对数据库进行 SQL 调用,无论是读取还是递增,以确保我们绕过任何可能通过 EF 记录的潜在缓存。

您可以将其作为单行来增加值:

dbContext.Database.ExecuteSqlCommand("UPDATE Setting SET[Counter] = IsNull([Counter],0) + 1 WHERE[Name] = 'Logins'");

或者如果你想检查新值:

int newCount = dbContext.Database.SqlQuery<int>(@"
    UPDATE Setting SET[Counter] = IsNull([Counter],0) + 1
    OUTPUT inserted.[Counter]
    WHERE [Name] = 'Logins'").First();

如果您需要获取当前值,并且知道它是最新的,那么您可以以相同的方式从任何上下文中简单地查询它:

int logins = dbContext.Database.SqlQuery<int>(@"
    SELECT [Counter] FROM Setting 
    WHERE [Name] = 'Logins'").First();

我希望这能阐明为什么你的代码只增加了一次值,这不是 EF 的错误,只是我们需要注意的一点,一旦 EF 从数据库中读取值,它们可能已经 陈旧或过时。如果乐观并发不适合您的用例,那么您需要跳出框框思考一下;)

【讨论】:

    【解决方案3】:

    简单的方法? 那么我建议在 EF Core 中使用手动事务
    ef core transaction docs

    一定要添加某种类型的唯一约束。 (设置 id + 登录计数器)

    using(var transaction = _context.Database.BeginTransaction())
    {
      try 
      {
       
        var totalLoginsCounter = _context.Settings.FirstOrDefault(x => x.Name == "Logins").Counter;
    
        totalLoginsCounter += 1;
    
        await _context.SaveChanges();
    
        transaction.Commit();
      }
      catch
      {  
        commit.RollBack();
      }
    
    }
    

    如果并发发生,请求将失败。因为它会尝试放置不可能的重复键。那么强烈建议您实施重试模式,以避免人们因为您的数据库中的号码未更新而无法实际登录。

    【讨论】:

      猜你喜欢
      • 2019-07-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多