【问题标题】:How to prevent retrieving duplicate values from the db using entityframework如何防止使用实体框架从数据库中检索重复值
【发布时间】:2018-02-19 13:57:35
【问题描述】:

我有一种情况,我在名为 Profile 的表中有一个计数器字段,在提交表单时,我将检索计数器字段并 +1 到计数器并更新配置文件表。递增的计数器将存储在一个变量中,然后我将使用该变量在另一个表 [Bidder] 中创建新记录。问题是当多个表单同时提交时,Bidder表中会产生重复的记录值

Profile profile = db.Profile.Where(w => w.TenderId == tender_Id && w.IsDeleted == false).FirstOrDefault();
int submission = profile.TotalSubmission + 1;

if (profile != null) {
    profile.TotalSubmission = submission;
    profile.ModifiedBy = user_id;
    profile.ModifiedOn = DateTime.Now;
    db.Entry(profile).State = EntityState.Modified;
    db.SaveChanges();
}

bid.ROId = string.Format("RO{0}", submission);
db.Entry(bid).State = EntityState.Modified;
db.SaveChanges();

如何防止创建重复的 ROId?

【问题讨论】:

  • 在列上创建唯一索引。或者更好的是,使用标识列
  • @Rhumborl,到哪个表?简介或投标人?
  • 您可以在代码周围加上一个“锁”,这样可以直接解决您的问题,而无需更改太多代码。正如@Rhumborl 所说,我建议通过使用数据库生成密钥(即,如果 SQL Server 是一个标识列)来寻找一种创建唯一 id 的不同方法。
  • @GaryHolland:如果代码在多台机器上运行,锁将不起作用。此外,在同一台机器上,这段代码只能运行一次,而其他线程被锁定。这会产生巨大的性能问题,因为您在锁定期间有整个数据库往返。
  • if (profile != null) { 没用。此时,如果profilenull,则会在上一行抛出异常。

标签: c# entity-framework entity-framework-6


【解决方案1】:

您的解决方案不能只依赖实体框架。只有数据库拥有存储数据的全貌。您的不同实体上下文实例甚至不知道是否存在其他实例,因此在 EF 级别上协调全局范围内的序列号非常困难。

根据冲突的频率,我想到了两个选项来强制序列号的唯一性:

  1. 唯一约束
  2. 写入数据的存储过程

唯一约束

您可以在ProfileIdSequence 列上创建UNIQUE 约束。当您存储具有重复序列号的数据时,您将得到一个异常。异常本身或其内部异常之一将是SqlException。您可以检查该异常的错误号,如果它是error number 2627(如果您的 DBMS 是 SQL Server;如果不是,请检查 DBMS 中是否存在类似错误),您知道这是唯一键约束违规。在这种情况下,您从 DB 中获取当前序列号并使用新序列再次写入数据。您必须重复该操作,直到插入成功。

如果您使用的是 SQL Server,您可以像这样选择性地处理 UNIQUE KEY 约束违规(使用 C# 6.0 异常过滤器):

private bool IsUniqueKeyViolation(Exception exception) {
    Exception currentException = exception;
    while (currentException != null) {
        SqlException sqlException = exception as SqlException;
        if (sqlException != null) {
            return sqlException.Errors.Cast<SqlError>().Any(error => error.Number == 2627);
        }
        currentException = currentException.InnerException;
    }
    return false;
}

//...

//...Code to set up the POCOs before Save...
while(true) {
    try {
        context.SaveChanges();
    }
    catch(Exception exc) when (IsUniqueKeyViolation(exc)) {
        //...Code to update the sequence number...
        continue;
    }
    break;
}

此解决方案仅在预计冲突数量很少时才实用。如果冲突的数量很大,您会看到很多不成功的UPDATE 请求到数据库,这可能会成为性能问题。

编辑:

正如其他一些答案所建议的那样,您还可以将乐观并发与时间戳列一起使用。只要您只从自己的代码更新数据库,就可以正常工作。但是,UNIQUE KEY 约束也将保护您的数据的完整性免受并非源自您的应用程序的更改(如迁移脚本等)的影响。乐观并发不会给你同样的保证。

存储过程

您可以创建一个存储过程,该过程将从相同 INSERTUPDATE 语句中的最后一个现有数字设置新的序列号。存储过程可以将新的序列号返回给客户端,您可以进行相应的处理。

由于此解决方案将始终在单个语句中更新数据库,因此它适用于大量冲突更新。缺点是你必须在DB层面用SQL编写你的程序逻辑的一部分。

【讨论】:

  • 这是不正确的 - 实体框架可以通过 Code First 创建唯一索引。此外,如果您使用的是 DDD,那么数据库完全由您的应用程序控制,并且应用程序是具有全貌的应用程序,而不是数据库。
  • 嗯?所以数据库关心唯一索引/键是先用代码还是数据库创建的?设计方法如何改变数据库的工作方式?您说的是运行时问题的设计时间。
  • 我的问题在于声明 You can not rely only on entity framework for your solution,因为您可以先使用代码通过 EF 解决这个问题。
  • @DannyVarod 声明是正确的。 EF 没有任何规定来确保索引是唯一的。那只能由数据库处理。是否先使用 EF 代码来创建唯一约束并不重要。约束由 DB 处理,而不是 EF。投反对票的唯一原因是提出您的问题。
  • 我从不投反对票,除非我认为某个答案具有误导性或错误,事实上我经常投赞成票,即使我认为我的答案更好。我只是回答,因为我不同意你的回答——我不催乳。
【解决方案2】:

应使用唯一索引或唯一约束来强制执行唯一性。

您可以先使用代码创建这些(来自MSDN):

public class User
{
    public int UserId { get; set; }

    [Index(IsUnique = true)]
    public string Username { get; set; }

    public string DisplayName { get; set; }
}

或直接通过数据库。

应该使用乐观并发来保护计数器:

public class MyEntity
{
    [Key]
    public Guid Id { get; set; }

    // Add a timestamp property to your class
    [Timestamp]
    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    [ConcurrencyCheck]
    public byte[] VersionTimestamp { get; set; }

    public int Counter { get; set; }
}

如果您在更改后尝试使用VersionTimestamp 更新行而不从数据库中重新读取它,您将获得OptimisiticConcurrencyException,例如在这个测试场景中:

// Read the entity
MyEntity entity;
using (var context = new MyContext())
{
    entity = context.MyEntities.Single(e => e.Id == id1);
}

// Read and update the entity
using (var context = new MyContext())
{
    var entity2 = context.MyEntities.Single(e => e.Id == id1);
    entity2.Counter++;
    context.SaveChanges();
}

// Try to update stale data
// - an OptimisticConcurrencyException will be thrown
using (var context = new MyContext())
{
    entity.Counter++;
    context.SaveChanges();
}

【讨论】:

    【解决方案3】:

    如果您使用的是 SQL Server 2012 或更高版本,则可以使用Sequence 来完成此操作。您还希望通过唯一性约束来强制执行唯一性。

    public partial class YourEfContext : DbContext 
    {
        .... (other EF stuff) ......
    
        // get your EF context
        public int GetNextSequenceValue()
        {
            var rawQuery = Database.SqlQuery<int>("SELECT NEXT VALUE FOR dbo.SomeSequence;");
            var task = rawQuery.SingleAsync();
            int nextVal = task.Result;
    
            return nextVal;
        }
    }
    

    如果您没有支持序列的版本,另一种选择是使用数据库上的存储过程来发布 ID 号。存储过程可以与 ID 表一起工作,它可以在其上放置显式锁定。这意味着您可以从 proc 请求一个 id,它可以锁定表、读取当前数字、递增它、将其存储回表中、释放锁并返回 id。您需要从代码中调用您的 proc 以获取要分配的新 id。 db 端的锁确保您只被分配了唯一值。只要你的 id 列只被赋予了一个由 proc 分配的值,你就会有唯一的值。不过,您仍然可以分配任意数字,其中可能包括重复的数字,这可以通过唯一约束来解决。

    在实体框架中没有这些,尽管您仍然可以通过实体框架以一种或另一种方式访问​​所有这些。

    【讨论】:

      猜你喜欢
      • 2017-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-28
      • 1970-01-01
      • 2018-01-16
      • 1970-01-01
      • 2013-12-26
      相关资源
      最近更新 更多