2

I have an ASP.Net MVC3 solution running a batch import process which fetches data from a web-service. For each row/loop, the process needs to send up to four emails. I'd like to fire these emails off in background threads so that the main thread doesn't have to wait for the emails to be sent. The child email thread needs to update the database audit table on email send completion or failure.

The issue I'm having is that I use Unity to inject the IEmailer class into my main process thread, which also assigns the 'main process thread' datacontext into the emailer class. So I get errors when the datacontext has already been closed when the emailer tries to update the audit table if the main loop has already finished (a plausible scenario).

How do I tell Unity to assign a new datacontext to my new emailer threads, or how do I tell my emailer class to use a different unity container (configured with Transient datacontext, I guess?)?

Here's my stripped down code. (I realise I could just instantiate a 'new MyDataContext()' inside the emailer but there is definitely a better way).

Any help, suggestions, ideas or comments will be greatly appreciated - thank you!

IOC Container

this.unityContainer = new UnityContainer()
    .RegisterType<IDataProvider, DataProvider>()
    .RegisterType(typeof(IEmailer), typeof(Emailer))
    .RegisterType<DbContext, MyDataContext>(new HierarchicalLifetimeManager());

Import class (main thread)

public class DataSyncer : IDataSyncer
{
    public DataSyncer(IDataProvider dataProvider, IEmailer emailer)            {
            this.dataProvider = dataProvider;
            this.emailer = emailer;
        }

    public void Import(Guid key)        {
        // some import code
        emailer.EmailAddress = "someone@somewhere.com";
        emailer.Subject = "subject line";
        new Thread(emailer.SendMail).Start(); // send email in new thread
    }
}

Emailer class (for child threads)

public class Emailer : IEmailer
{
    [Dependency]
    public IDataProvider DataProvider { get; set; }
    // etc 
} 

DataProvider (contains datacontext via ctor injection)

public DataProvider(MyDataContext context, // etc) { // etc } 
tereško
  • 58,060
  • 25
  • 98
  • 150
keithl8041
  • 2,383
  • 20
  • 27

1 Answers1

1

I'm trying to rephrase your explanation to see if I got it right.

Your importer runs on the main thread. You fire of emails for every row you import. Your emailer needs to write audit information to the database upon success or failure of the process.

The emailer is injected into your importer and both have a dependency on a class derived from DbContext? Is that the same instance of the DbContext? If so: Why do you share that instance? Isn't each task of sending an email independent from all other tasks? If so, remove the HierarchicalLifetimeManager.

You use property injection for your IDataProvider. I understood that this is a must-have dependency. If that is the case you should use constructor injection like you already do for the other classes. By the way: Don't use the DependencyAttribute. You can also configure property injection using InjectionProperty in your call to RegisterType.


Update

As far as I know Unity never cleans up after itself. Meaning I would not expect it to call Dispose on your DbContext anyway. Do you have a reference where it says that the HierarchicalLifetimeManager disposes objects properly? I would be very interested to read it!

HierarchicalLifetimeManager works the same way as ContainerControlledLifetimeManager as long as you don't deal with child containers. That basically means that you have a single instance of your context across all threads. If you just remove that lifetime manager you would get a new instance whenever one is needed as a dependency. That should solve your problem.

If you need to take care of the disposal of your context instances I would inject a factory for the context instead of an instance. Just declare a ctor parameter of Type Func<MyDataContext> Unity will automatically generate the delegate for you (that feature is called automatic factories btw.). Then you can use using(var ctx = dbContextFactory()) { ... }.

Sebastian Weber
  • 6,766
  • 2
  • 30
  • 49
  • Thanks for taking the time to go through my question. Yes, your re-phrasing is logically correct. The `DbContext` is shared amongst entities which works well in the app but obviously not across threads. I'm new to DI and the reading that I've done points towards using `HierarchicalLifetimeManager` for the `DbContext` to ensure that it gets properly disposed. I'm using an EF-CodeFirst approach (not sure if it makes a difference?). How would I configure the emailer alone to use a new `DbContext` instance? – keithl8041 Mar 14 '12 at 15:28
  • Thanks for the update. I read a great article on how Unity doesn't dispose of objects properly but now I can't find it. Closest I could come to was this one [link](http://www.ladislavmrnka.com/2011/03/unity-build-in-lifetime-managers/) which explains the `HierarchicalLifetimeManager` (and others) in more detail. Are you saying I should ditch the `HLTM` and go with transient (the default) for everything? I did try that previously but quickly started finding inconsistencies in the data returned from the `DbContext`. Interesting tip on the automatic factories, I'll look into that, cheers! – keithl8041 Mar 14 '12 at 16:21
  • @keithl8041 What kind of inconsistencies did you get? As soon as you call `SaveChanges` on your `DbContext` those changes will be persisted in your database. As long as that has not happened all changes will only exist in the local store of the context. – Sebastian Weber Mar 15 '12 at 08:39
  • I was getting data back which I shouldn't have been, is the only way to describe it. As if the datacontext was caching or wasn't clearing out data. I'll try it again and report back on specifics. Incidentally, [here's the article](http://www.devtrends.co.uk/blog/introducing-the-unity.mvc3-nuget-package-to-reconcile-mvc3-unity-and-idisposable) about disposing datacontext with mvc3. Note that I'm not actually using the Unity.MVC3 implementation, just pointing out the article for reference. – keithl8041 Mar 15 '12 at 12:13
  • Yep confirmed, I have emptied all data from a table in one action (with SaveChanges()), then run a method which requests that data _which returns the full dataset_, despite there being no actual data in the database table. I did the reverse of adding data (with SaveChanges()) and the data was not added to the database although it is available in the context. Its like the `DbContext` seems to be caching the old dataset from prior requests. Going back to `AsHierarchicalControlled()` makes the context work as I would expect. I'm not convinced that removing the HTLM is a valid solution. – keithl8041 Mar 15 '12 at 13:29
  • It's not a bug, it's a "feature" of Entity Framework. See [this question](http://stackoverflow.com/questions/3617987/ef-4-0-model-caching-the-data-and-does-not-detect-the-modified-data). – Sebastian Weber Mar 15 '12 at 13:47
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/8926/discussion-between-keithl8041-and-sebastian-weber) – keithl8041 Mar 15 '12 at 13:57