3

I have an MVC3/.NET 4 application which uses Entity Framework (4.3.1 Code First)

I have wrapped EF into a Repository/UnitOfWork pattern as described here…

http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

Typically, as it explains in the article, when I require the creation of a new record I’ve been doing this…

public ActionResult Create(Course course)
{
   unitOfWork.CourseRepository.Add(course);
   unitOfWork.Save();
   return RedirectToAction("Index");
}

However, when more than simply saving a record to a database is required I wrap the logic into what I’ve called an IService. For example…

private ICourseService courseService;
public ActionResult Create(Course course)
{
   courseService.ProcessNewCourse(course);
   return RedirectToAction("Index");
}

In one of my services I have something like the following…

public void ProcessNewCourse(Course course)
{
    // Save the course to the database…
    unitOfWork.CourseRepository.Add(course);
    unitOfWork.Save();

    // Generate a PDF that email some people about the new course being created, which requires more use of the unitOfWork…
    var someInformation = unitOfWork.AnotherRepository.GetStuff();
    var myPdfCreator = new PdfCreator();

    IEnumerable<People> people = unitOfWork.PeopleRepository.GetAllThatWantNotifiying(course);

    foreach(var person in people)
    {
        var message = “Hi ” + person.FullName;
        var attachment = myPdfCreator.CreatePdf();
        etc...
        smtpClient.Send();        
    }
}

The above isn’t the actual code (my app has nothing to do with courses, I’m using view models, and I have separated the PDF creation and email message out into other classes) but the gist of what is going on is as above!

My problem is that the generation of the PDF and emailing it out is taking some time. The user just needs to know that the record has been saved to the database so I thought I would put the code below the unitOfWork.Save(); into an asynchronous method. The user can then be redirected and the server can happily take its time processing the emails, and attachments and whatever else I require it to do post save.

This is where I’m struggling.

I’ve tried a few things, the current being the following in ICourseService…

public class CourseService : ICourseService
{

    private delegate void NotifyDelegate(Course course);
    private NotifyDelegate notifyDelegate;

    public CourseService()
    {
        notifyDelegate = new NotifyDelegate(this.Notify);
    }

    public void ProcessNewCourse(Course course)
    {
        // Save the course to the database…
        unitOfWork.CourseRepository.Add(course);
        unitOfWork.Save();

        notifyDelegate.BeginInvoke(course);
    }

    private void Notify(Course course)
    {
        // All the stuff under unitOfWork.Save(); moved here.   
    }
}

My Questions/Problems

I’m randomly getting the error: "There is already an open DataReader associated with this Command which must be closed first." in the Notify() method.

  1. Is it something to do with the fact that I’m trying to share the unitOrWork and therefore a dbContext across threads?

  2. If so, can someone be kind enough to explain why this is a problem?

  3. Should I be giving a new instance of unitOfWork to the Notify method?

  4. Am I using the right patterns/classes to invoke the method asynchronously? Or should I be using something along the lines of....

    new System.Threading.Tasks.Task(() => { Notify(course); }).Start();

    I must say I've become very confused with the terms asynchronous, parallel, and concurrent!!

  5. Any links to articles (c# async for idiots) would be appreciated!!

Many thanks.

UPDATE:

A little more digging got me to this SO page: https://stackoverflow.com/a/5491978/192999 which says...

"Be aware though that EF contexts are not thread safe, i.e. you cannot use the same context in more than one thread."

...so am I trying to achieve the impossible? Does this mean I should be creating a new IUnitOfWork instance for my new thread?

Community
  • 1
  • 1
ETFairfax
  • 3,794
  • 8
  • 40
  • 58
  • 1
    I suggest creating another Service class and move your Notify method there. In that new Service, make sure that you have a constructor that would create a new unitOfWork with a different DbContext than the one you used in the main thread. Or better yet, move all database-related calls in your main thread and keep the spawned thread DB call-free. – rikitikitik Jun 13 '13 at 08:26
  • @rikitikitik Thanks, I was just doing as you've suggested, and have spawned a new unitOfWork, and all seems to be working. I've just got to tidy things up now in the constructors. Re: leaving all DB calls on the main thread. That would mean the user having to wait on all the db calls to finish, which I want to avoid. – ETFairfax Jun 13 '13 at 08:59
  • Glad that you had it working. If the DB calls are taking a long time as well (I though it was just the PDF creation), then yeah, move them. – rikitikitik Jun 13 '13 at 09:50
  • This may not be exactly what you are looking for, but EF6 (which recently hit beta) supports the async/await keywords that are new to .NET 4.5. http://odetocode.com/blogs/scott/archive/2012/08/28/async-in-entity-framework-6-0.aspx – smudge Jun 13 '13 at 20:23
  • Would it be overkill to recommend http://particular.net/nservicebus ? – Rosdi Kasim Jun 17 '13 at 16:01
  • @RosdiKasim : Maybe.....maybe not. Looks like I've got more stuff to read/learn! Thanks for the link. – ETFairfax Jun 17 '13 at 23:47
  • I'm not sure "fire and forget" style tasks are a very good idea from within asp.net applications – Pablo Romeo Jun 25 '13 at 04:44

1 Answers1

0

You could create a polling background thread that does the lengthy operation separately from your main flow. This thread could scan the database for new items (or items marked to process). This solution is pretty simple and ensures that jobs get done even if you application crashes (it will be picked up when the polling thread is started again).

You could also use a Synchronised Queue if it's not terrible if the request is 'lost', in the case your application crashes after the doc is requested and before it's generated/sent.

One thing is almost sure - as rikitikitik said - you will need to use a new unit of work, which means a separate transaction.

You could also look at Best threading queue example / best practice .

Community
  • 1
  • 1
tymtam
  • 31,798
  • 8
  • 86
  • 126