我们有多个并发线程,它们可以同时从数据库表中“声明”未处理的记录以进行处理。为了确保没有线程最终要求重复的记录,我们使用一个看起来像这样的查询。
WITH UpdateView AS
(
SELECT TOP 1 X, Y, Z, Processed
FROM MyTable
WHERE Processed = 0
)
UPDATE UpdateView
SET Processed = 1
OUTPUT INSERTED.X, INSERTED.Y, INSERTED.Z, INSERTED.Processed
我的问题是,是否有等效的方法可以通过Entity Framework 6.0+执行类似的工作,或者这不是EF的好用例吗?
更新
所以我要寻找的东西大概与此等效:
IEnumerable<MyTable> results = context.MyTable
.Where(r => !r.Processed)
.Take(1) // Up to this point, it effectively builds the UPdateView portion of the original query but thanks to deferred execution, no data is actually retrieved yet.
.UpdateAndReturn(context, r => r.Processed = true); // Hypothetical extension method that would perform the update and retrieve the updated record(s) as an atomic operation.
不幸的是,我不知道如何通过EF将更新操作添加到延迟执行linq查询中。
答案 0 :(得分:1)
保留记录的行为应由单个线程上的单个上下文整理。例如,给定几个工作线程,每个工作线程都有自己的DbContext,您将遇到这样的情况,即遇到每个工作人员最终可能同时查询Processed = 0并出现重叠的情况。
相反,我可能会考虑在记录中添加ProcessorId列,并将ID与每个工作线程相关联。每个工作线程都会查询ProcessorId = MyProcessorId && Processed == 0的位置。当工作线程中没有未处理的记录时,它将调用其处理器ID为Marsled的编组单线程,该线程的处理器ID为NULL,Processed == 0,然后分配一个或多个行到处理器ID并返回。根据处理量,您可以一次分配一个,也可以批量分配10/50/100等。
更新: 通过使用事务锁定表记录足够长的时间来选择一个记录并将其设置为“已处理”标志,您可能能够以线程安全的方式保留记录。如果记录是“扁平”的,没有任何参考等问题,那么您可以在设置标志后将其与将要进行处理等操作的非tx范围内的上下文相关联而将其分离,而无需锁定表。否则,只需获取记录ID,然后在阻塞Tx完成后根据需要重新加载它即可。
即
UpdateView view = null;
using(var context = new MyContext())
{
using (var tx = context.Database.BeginTransaction())
{
view = context.UpdateViews
.Where(x => !x.IsProcessed)
.OrderBy(x => x.CreatedDate)
.FirstOrDefault();
if (view != null)
{
view.IsProcessed = true;
context.SaveChanges();
context.Detach(view);
}
tx.Commit();
}
}
if(view == null)
return;
using(var context = new MyContext())
{
context.UpdateViews.Attach(view);
// continue processing...
}
您可能不需要2x上下文,只需在Tx范围之外使用相同的上下文就可以了,但是我只是把选项放在了那里以防万一。如果您想紧急加载子项或相关实体,那么我将保留预订而只读取记录而不进行紧急加载,设置已处理的标志并获取ID,然后在Tx关闭之后,再次加载实体/ w渴望加载。这样可以使读取和提交之间的阻塞时间尽可能短。