我有一种情况,我在名为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?
答案 0 :(得分:0)
您不能仅依赖实体框架来解决问题。只有数据库才能全面了解存储的数据。您的不同实体上下文实例甚至不知道是否存在其他实例,因此在EF级别上协调全局范围内的序列号非常困难。
根据冲突的频率,我想到两个选项来强制执行序列号的唯一性:
唯一约束
您可以在UNIQUE
和ProfileId
列上创建Sequence
约束。使用重复的序列号存储数据时,您将收到异常。异常本身或其内部异常之一将是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
约束将保护数据的完整性,同时保护数据不会来自不是来自应用程序的更改(如迁移脚本等)。乐观并发不会给你相同的保证。
存储过程
您可以创建一个存储过程,该存储过程将从相同的 INSERT
或UPDATE
语句中的最后一个现有数字设置新的序列号。存储过程可以将新的序列号返回给客户端,您可以相应地处理它。
由于此解决方案将始终在单个语句中更新数据库,因此适用于大量冲突更新。缺点是您必须在DB级别的SQL中编写程序逻辑的一部分。
答案 1 :(得分:0)
应使用唯一索引或唯一约束来强制执行唯一性。
您可以先使用代码创建这些代码(来自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();
}
。在这个测试场景中:
String randomPass = passwordEncoder.generateRand()
答案 2 :(得分:0)
如果您使用的是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。数据库端的锁定确保您只被分配了唯一值。只要您的id列只被赋予proc指定的值,您将拥有唯一值。您仍然可以分配任意数字,可以包括重复项,可以使用唯一约束来解决。
在Entity-Framework中没有特定的,尽管你仍然可以通过某种方式通过实体框架访问所有这些。