EntityFramework 并处理重复的主要 key/concurrency/race 条件情况
EntityFramework and handling duplicate primary key/concurrency/race conditions situations
我写了一个被许多应用程序引用的库,它跟踪谁在线以及他们正在查看哪个应用程序和页面。
数据使用 EF6 存储在 Sql Server 2008 table 中,它跟踪用户名(主键)、应用程序、页面和时间戳。我只想存储每个人的最新请求,因此每个用户名只能存储一次。
从每个应用程序的 Global.asax 调用的库代码如下所示:
public static void Add(ApplicationType application, string username, string pageRequested)
{
using (var db = new CommonDAL()) // EF context
{
var exists = db.ActiveUsers.Find(username);
if (exists != null)
db.ActiveUsers.Remove(exists);
var activeUser = new ActiveUser() { ApplicationID = application.Value(), Username = username, PageRequested = pageRequested, TimeRequested = DateTime.Now };
db.ActiveUsers.Add(activeUser);
db.SaveChanges();
}
}
我间歇性地收到错误 Violation of PRIMARY KEY constraint 'PK_tblActiveUser_Username'. Cannot insert duplicate key in object 'dbo.tblActiveUser'. The duplicate key value is (xxxxxxxx)
我只能猜测发生的是请求 A 进来,删除了现有的用户名。然后请求 B(来自同一用户)进来,试图删除用户名,但发现什么都不存在。然后请求 A 添加用户名。然后请求 B 尝试添加用户名。当 Web 服务器向客户端发送 401 状态时,似乎经常触发该错误,这再次指向触发此错误的短时间内的多个请求。
我在使用单元测试模拟这种竞争条件时遇到了麻烦,因为我之前没有做过太多的异步编程,但我试图创建具有延迟的异步测试来模拟多个同时发生的缓慢请求。我尝试使用 using (var transaction = new TransactionScope())
和 using (var transaction = db.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
来锁定请求,以便请求 A 可以在请求 B 开始之前完成,但无法验证其中任何一个都解决了问题,因为我无法可靠地模拟这种情况。
1)防止异常的正确方法是什么(最近的请求是最终存储的请求)?
2) 编写单元测试以证明其有效的正确方法是什么?
因为你只想存储最新的项目,你可以使用最后更新获胜并避免关于谁可以先插入的竞争条件,数据库处理锁和最后调用更新(这是最近的) 是 table.
中的内容
如果您 运行 遇到边缘情况下的并发问题,即全新用户同时有 2 个请求并避免 "infinite" 循环,则类似以下内容应该可以处理任何主键错误错误(直到出现堆栈溢出异常为止)。
public static void Add(ApplicationType application,
string username,
string pageRequested,
int recursionCount = 0)
{
using (var db = new CommonDAL()) // EF context
{
var exists = db.ActiveUsers.Find(username);
if (exists != null)
{
exists.propa = "someVal";
}
else
{
var activeUser = new ActiveUser
{
ApplicationID = application.Value(),
Username = username,
PageRequested = pageRequested,
TimeRequested = DateTime.Now
};
db.ActiveUsers.Add(activeUser);
}
try
{
db.SaveChanges();
}
catch(<Primary Key Violation>)
{
if(recursionCount < x)
{
Add(application, username, pageRequested, recursionCount++)
}
else
{
throw;
}
}
}
}
至于对此进行单元测试,除非您插入人为延迟或可以强制两个线程同时 运行,否则将非常困难。有时,根据问题,竞争条件的时间在毫秒范围内。任务可能无法工作,因为它们不能保证同时 运行,你将它们扔到后台线程池,它们可以时 运行。旧式线程可能有效,但我不知道如何强制执行它,因为读取和删除和创建之间的时间很可能在 5 毫秒或更短的范围内。
我写了一个被许多应用程序引用的库,它跟踪谁在线以及他们正在查看哪个应用程序和页面。
数据使用 EF6 存储在 Sql Server 2008 table 中,它跟踪用户名(主键)、应用程序、页面和时间戳。我只想存储每个人的最新请求,因此每个用户名只能存储一次。
从每个应用程序的 Global.asax 调用的库代码如下所示:
public static void Add(ApplicationType application, string username, string pageRequested)
{
using (var db = new CommonDAL()) // EF context
{
var exists = db.ActiveUsers.Find(username);
if (exists != null)
db.ActiveUsers.Remove(exists);
var activeUser = new ActiveUser() { ApplicationID = application.Value(), Username = username, PageRequested = pageRequested, TimeRequested = DateTime.Now };
db.ActiveUsers.Add(activeUser);
db.SaveChanges();
}
}
我间歇性地收到错误 Violation of PRIMARY KEY constraint 'PK_tblActiveUser_Username'. Cannot insert duplicate key in object 'dbo.tblActiveUser'. The duplicate key value is (xxxxxxxx)
我只能猜测发生的是请求 A 进来,删除了现有的用户名。然后请求 B(来自同一用户)进来,试图删除用户名,但发现什么都不存在。然后请求 A 添加用户名。然后请求 B 尝试添加用户名。当 Web 服务器向客户端发送 401 状态时,似乎经常触发该错误,这再次指向触发此错误的短时间内的多个请求。
我在使用单元测试模拟这种竞争条件时遇到了麻烦,因为我之前没有做过太多的异步编程,但我试图创建具有延迟的异步测试来模拟多个同时发生的缓慢请求。我尝试使用 using (var transaction = new TransactionScope())
和 using (var transaction = db.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
来锁定请求,以便请求 A 可以在请求 B 开始之前完成,但无法验证其中任何一个都解决了问题,因为我无法可靠地模拟这种情况。
1)防止异常的正确方法是什么(最近的请求是最终存储的请求)?
2) 编写单元测试以证明其有效的正确方法是什么?
因为你只想存储最新的项目,你可以使用最后更新获胜并避免关于谁可以先插入的竞争条件,数据库处理锁和最后调用更新(这是最近的) 是 table.
中的内容如果您 运行 遇到边缘情况下的并发问题,即全新用户同时有 2 个请求并避免 "infinite" 循环,则类似以下内容应该可以处理任何主键错误错误(直到出现堆栈溢出异常为止)。
public static void Add(ApplicationType application,
string username,
string pageRequested,
int recursionCount = 0)
{
using (var db = new CommonDAL()) // EF context
{
var exists = db.ActiveUsers.Find(username);
if (exists != null)
{
exists.propa = "someVal";
}
else
{
var activeUser = new ActiveUser
{
ApplicationID = application.Value(),
Username = username,
PageRequested = pageRequested,
TimeRequested = DateTime.Now
};
db.ActiveUsers.Add(activeUser);
}
try
{
db.SaveChanges();
}
catch(<Primary Key Violation>)
{
if(recursionCount < x)
{
Add(application, username, pageRequested, recursionCount++)
}
else
{
throw;
}
}
}
}
至于对此进行单元测试,除非您插入人为延迟或可以强制两个线程同时 运行,否则将非常困难。有时,根据问题,竞争条件的时间在毫秒范围内。任务可能无法工作,因为它们不能保证同时 运行,你将它们扔到后台线程池,它们可以时 运行。旧式线程可能有效,但我不知道如何强制执行它,因为读取和删除和创建之间的时间很可能在 5 毫秒或更短的范围内。