结合使用实体框架和任务并行库 [英] Using Entity framework in conjunction with Task Parallel Library

查看:70
本文介绍了结合使用实体框架和任务并行库的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个正在使用.NET 4.0和EF 6.0开发的应用程序.该程序的前提非常简单.观看文件系统上的特定文件夹.将新文件放入该文件夹后,请在SQL Server数据库中(使用EF)查找有关此文件的信息,然后根据找到的内容将文件移动到文件系统上的另一个文件夹.文件移动完成后,返回数据库并更新有关此文件的信息(注册文件移动).

I have an application that we are developing using .NET 4.0 and EF 6.0. Premise of the program is quite simple. Watch a particular folder on the file system. As a new file gets dropped into this folder, look up information about this file in the SQL Server database (using EF), and then based on what is found, move the file to another folder on the file system. Once the file move is complete, go back to the DB and update the information about this file (Register File move).

这些是大型媒体文件,因此每个文件可能需要一段时间才能移动到目标位置.此外,我们可能会使用数百个位于源文件夹中的媒体文件来启动此服务,而这些媒体文件将需要分派到目标位置.

These are large media files so it might take a while for each of them to move to the target location. Also, we might start this service with hundreds of these media files sitting in the source folder already that will need to be dispatched to the target location(s).

因此,为了加快处理速度,我首先使用Task并行库(由于.NET 4.0无法使用async/await).对于源文件夹中的每个文件,我都会在数据库中查找有关该文件的信息,确定它需要移动到哪个目标文件夹,然后启动一个新任务,开始移动文件...

So to speed things up, I started out with using Task parallel library (async/await not available as this is .NET 4.0). For each file in the source folder, I look up info about it in the DB, determine which target folder it needs to move to, and then start a new task that begins to move the file…

LookupFileinfoinDB(filename)
{
  // use EF DB Context to look up file in DB
}

// start a new task to begin the file move
var moveFileTask = Task<bool>.Factory.StartNew(
                () =>
                    {
                        var success = false;

                        try
                        {
                         // the code to actually moves the file goes here…
                         .......
                         }
                      }

现在,一旦此任务完成,我就必须返回数据库并更新有关文件的信息.这就是我遇到问题的地方. (请记住,我可能同时运行多个移动文件任务",它们将在不同的时间完成.目前,我正在使用任务连续性在DB中注册文件移动:

Now, once this task completes, I have to go back to the DB and update the info about the file. And that is where I am running into problems. (keep in mind that I might have several of these 'move file tasks'running in parallel and they will finish at different times. Currently, I am using task continuations to register the file move in the DB:

filemoveTask.ContinueWith(
                       t =>
                       {
                           if (t.IsCompleted && t.Result)
                           {
                             RegisterFileMoveinDB();
                           }
                       }

问题是我正在使用相同的数据库上下文来查找主任务以及稍后在嵌套任务上执行的RegistetrFilemoveinDB()方法内部的文件信息.当一起移动几个文件时,我会遇到各种各样的奇怪异常(主要是关于SQL Server Data Reader等).在线搜索答案表明,像我在这里所做的那样,在多个任务之间共享数据库上下文是一个很大的问题,因为EF并不是线程安全的.

Problem is that I am using the same DB context for looking up the file info in the main task as well as inside the RegistetrFilemoveinDB() method later, that executes on the nested task. I was getting all kinds of weird exceptions thrown at me (mostly about SQL server Data reader etc.) when moving several files together. Online search for the answer revealed that the sharing of DB context among several tasks like I am doing here is a big no no as EF is not thread safe.

我不想为每个文件移动创建一个新的数据库上下文,因为可能同时有数十个甚至数百个文件在运行.有什么好的替代方法?当嵌套任务完成并完成主任务中的文件移动注册时,是否可以发送信号"主任务?还是我以一种错误的方式共同解决了这个问题,并且有更好的方法来解决这个问题?

I would rather not create a new DB context for each file move as there could be dozens or even hundreds of them going at the same time. What would be a good alternative approach? Is there a way to 'signal' the main task when a nested task completes and finish the File move registration in the main task? Or am I approaching this problem in a wrong way all together and there is a better way to go about this?

推荐答案

您最好的选择是将DbContext的作用域设置为每个线程. Parallel.ForEach具有对此有用的重载(Func<TLocal> initLocal的重载:

Your best bet is to scope your DbContext for each thread. Parallel.ForEach has overloads that are useful for this (the overloads with Func<TLocal> initLocal:

Parallel.ForEach( 
    fileNames, // the filenames IEnumerable<string> to be processed
    () => new YourDbContext(), // Func<TLocal> localInit
    ( fileName, parallelLoopState, dbContext ) => // body
    {
        // your logic goes here
        // LookUpFileInfoInDB( dbContext, fileName )
        // MoveFile( ... )
        // RegisterFileMoveInDB( dbContext, ... )

        // pass dbContext along to the next iteration
        return dbContext;
    }
    ( dbContext ) => // Action<TLocal> localFinally
    {
        dbContext.SaveChanges(); // single SaveChanges call for each thread
        dbContext.Dispose();
    } );

如果您希望尽快更新数据库,则可以在主体表达式/RegisterFileMoveInDB中调用SaveChanges().我建议将文件系统操作与数据库事务绑定在一起,以便在数据库更新失败时回退文件系统操作.

You can call SaveChanges() within the body expression/RegisterFileMoveInDB if you prefer to have the DB updated ASAP. I would suggest tying the file system operations in with the DB transaction so that if the DB update fails, the file system operations are rolled back.

这篇关于结合使用实体框架和任务并行库的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆