Using URF without Unity/DI inside a Azure WebJob

Dec 10, 2015 at 4:48 PM
I have been using URF on many MVC web projects with great success (using EF & Unity). My latest project is on Azure and uses a WebJob for background processing. I figure out how to get Unity and DI working on the WebJob (using IJobActivator), but I think it's working against me. Because EF is not thread safe, when more than one WebJob instance triggers (from a queue), I get all sorts of concurrency errors (if I force one WebJob instance at a time it works fine). Based on some reading I've done, it looks like I have to create and dispose of the context within the single WebJob function.

So I think that means throwing out Unity and DI, and just newing up a direct instance of the DbContext, Services and Repos that I need and then dispose of them within the single function. However, I've never used URF without Unity/DI. So I am not totally sure how to actually do that within the bounds of URF. Perhaps I'm just being dense, but a quick pointer would help.

Thanks,
Bryan
Dec 10, 2015 at 6:41 PM
Nevermind, I got it figured out... :)
Coordinator
Dec 10, 2015 at 7:21 PM
Edited Dec 10, 2015 at 7:22 PM
With Unity and other DI/IoC frameworks you can configure your EF contest to have singleton like behavior:
           container.RegisterType<IDataContextAsync, NorthwindContext>(new PerRequestLifetimeManager())
            .RegisterType<IUnitOfWorkAsync, UnitOfWork>(new PerRequestLifetimeManager())
If your solution was different than this could you share this?
Dec 11, 2015 at 4:16 AM
Edited Dec 11, 2015 at 4:18 AM
I took my own advice and actually got rid of DI/IoC for this instance. Perhaps there is a way another around it, but the parallelism of WebJobs and the fact that EF is not thread-safe caused all sorts of problems. The general consensus I found online was that if you are using EF you must instantiated and dispose of your DbContext within the scope of the WebJob method, preferably by using a "using" statement. No matter what LifetimeManager I tried using, the DBContext was being shared across parallel WebJob instances and caused DB concurrency problems. Because my WebJob didn't make use of too many repos and services, I simply got rid of the DI and created direct instances of those classes inside of the using statement. It's not elegant, but it worked and I was able to use the default WebJob queue count of 16 and have up to 16 instances of the WebJob method running simultaneously without concurrency errors.

I am, however, running into a separate problem when deploying to Azure. When the WebJob starts, it seems to be triggering an additional Database Initilization despite the fact that my DbContext class has "Database.SetInitializer<MyContext>(null);" in the constructor. So when the WebJob starts, it nukes the database (the DB remains, but all tables and data get deleted). The Web App runs fine and the DB is fine, UNTIL the first trigger of the WebJob and then the DB goes bye-bye. I assume it's trying to run an initialization or migration when it shouldn't. Any thoughts?
Coordinator
Dec 11, 2015 at 5:04 AM
Edited Dec 11, 2015 at 5:04 AM
Sounds like you may have EF Migrations Initialize (think you may have already disabled this), SeedData implemented?
AutomaticMigrationsEnabled = false;
May want to also delete the EF Migrations folder if it's there.
Dec 11, 2015 at 5:11 AM
Yes, I have AutomaticMigrationsEnabled = false; in my Configuration.cs constructor. I do also have a Seed method in that class. Will the WebJob trigger the Migrations again even if they were already called and done via the Web App? The Web App and WebJob share my Data assembly which contains the DbContext class and the Migrations folder. But I didn't think the WebJob would try to re-initialize the DB again after it was already done and the migration finished.
Dec 11, 2015 at 6:08 PM
When I used detailed logging on Azure and it failed, it seems pretty obvious from the logs that the WebJob is trying to invoke a migration despite my best efforts. The log shows:
 at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute(Action operation)
   at System.Data.Entity.SqlServer.SqlProviderServices.UsingConnection(DbConnection sqlConnection, Action1 act)
   at System.Data.Entity.SqlServer.SqlProviderServices.UsingMasterConnection(DbConnection sqlConnection, Action1 act)
   at System.Data.Entity.SqlServer.SqlProviderServices.CreateDatabaseFromScript(Nullable1 commandTimeout, DbConnection sqlConnection, String createDatabaseScript)
   at System.Data.Entity.SqlServer.SqlProviderServices.DbCreateDatabase(DbConnection connection, Nullable1 commandTimeout, StoreItemCollection storeItemCollection)
   at System.Data.Entity.Core.Common.DbProviderServices.CreateDatabase(DbConnection connection, Nullable1 commandTimeout, StoreItemCollection storeItemCollection)
   at System.Data.Entity.Core.Objects.ObjectContext.CreateDatabase()
   at System.Data.Entity.Migrations.Utilities.DatabaseCreator.Create(DbConnection connection)
   at System.Data.Entity.Migrations.DbMigrator.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
   at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
   at System.Data.Entity.Internal.DatabaseCreator.CreateDatabase(InternalContext internalContext, Func3 createMigrator, ObjectContext objectContext)
   at System.Data.Entity.Internal.InternalContext.CreateDatabase(ObjectContext objectContext, DatabaseExistenceState existenceState)
   at System.Data.Entity.Database.Create(DatabaseExistenceState existenceState)
   at System.Data.Entity.DropCreateDatabaseIfModelChanges1.InitializeDatabase(TContext context)
   at System.Data.Entity.Internal.InternalContext.<>c__DisplayClassf1.<CreateInitializationAction>b__e()
   at System.Data.Entity.Internal.InternalContext.PerformInitializationAction(Action action)
   at System.Data.Entity.Internal.InternalContext.PerformDatabaseInitialization()
   at System.Data.Entity.Internal.LazyInternalContext.<InitializeDatabase>b__4(InternalContext c)
   at System.Data.Entity.Internal.RetryAction1.PerformAction(TInput input)
   at System.Data.Entity.Internal.LazyInternalContext.InitializeDatabaseAction(Action1 action)
   at System.Data.Entity.Internal.LazyInternalContext.InitializeDatabase()
   at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
   at System.Data.Entity.Internal.Linq.InternalSet1.Initialize()
   at System.Data.Entity.Internal.Linq.InternalSet1.get_InternalContext()
   at System.Data.Entity.Internal.Linq.InternalSet1.FindAsync(CancellationToken cancellationToken, Object[] keyValues)
   at System.Data.Entity.DbSet1.FindAsync(CancellationToken cancellationToken, Object[] keyValues)
   at System.Data.Entity.DbSet1.FindAsync(Object[] keyValues)
   at Repository.Pattern.Ef6.Repository1.<FindAsync>d__19.MoveNext()
Any additional thoughts why it my be ignoring my null SetInitializer, but only in the WebJob?