Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scoping Entity Framework using Ninject in Azure Functions

I'm having some issues scoping Entity Framework using Ninject within an Azure Function.

I keep getting random object already disposed and internal EF errors, such as the following, which leads me to believe the DbContext is being shared between threads:

I'm not sure if this is getting scoped wrong, or if i only need to be calling _kernal.Load() once per app domain. Any insight would be greatly appreciated.

An item with the same key has already been added.

at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource) at System.Collections.Generic.Dictionary'2.Insert(TKey key, TValue value, Boolean add)
at System.Data.Entity.Core.Objects.ObjectStateManager.AddStateManagerTypeMetadata(EntitySet entitySet, ObjectTypeMapping mapping)
at System.Data.Entity.Core.Objects.ObjectStateManager.GetOrAddStateManagerTypeMetadata(Type entityType, EntitySet entitySet)
at System.Data.Entity.Core.Objects.ObjectStateManager.AddEntry(IEntityWrapper wrappedObject, EntityKey passedKey, EntitySet entitySet, String argumentName, Boolean isAdded)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.HandleEntityAppendOnly[TEntity](Func'2 constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator'1.ReadNextElement(Shaper shaper)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper'1.RowNestedResultEnumerator.MaterializeRow() at System.Data.Entity.Core.Common.Internal.Materialization.Shaper'1.RowNestedResultEnumerator.MoveNext() at System.Data.Entity.Core.Common.Internal.Materialization.Shaper'1.ObjectQueryNestedEnumerator.TryReadToNextElement() at System.Data.Entity.Core.Common.Internal.Materialization.Shaper'1.ObjectQueryNestedEnumerator.MoveNext() at System.Data.Entity.Internal.LazyEnumerator'1.MoveNext()
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable'1 source)
at System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider.b__1[TResult](IEnumerable'1 sequence)
at System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider.ExecuteSingle[TResult](IEnumerable'1 query, Expression queryRoot)
at System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider.System.Linq.IQueryProvider.Execute[TResult](Expression expression)
at System.Data.Entity.Internal.Linq.DbQueryProvider.Execute[TResult](Expression expression)
at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable'1 source, Expression`1 predicate)
at MyApp.DAO.Implementations.LoanRepository.Get(Int32 loanId) in d:\a\1\s\MyApp\MyApp.DAO\Implementations\LoanRepository.cs:line 50
at MyApp.DAO.Implementations.LoanRepository.Get(String loanGuid) in d:\a\1\s\MyApp\MyApp\Implementations\LoanRepository.cs:line 0
at MyApp.BL.Los.MyManager.d__22.MoveNext()

and

The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.

at System.Data.Entity.Core.Objects.ObjectContext.ReleaseConnection() at System.Data.Entity.Core.Common.Internal.Materialization.Shaper'1.Finally() at System.Data.Entity.Core.Common.Internal.Materialization.Shaper'1.SimpleEnumerator.Dispose() at System.Data.Entity.Internal.LazyEnumerator`1.Dispose() at MyApp.DAO.Implementations.PromotionRepository.getAllActivePromotions(Int32 LoanID) in d:\a\1\s\MyApp\MyApp.DAO\Implementations\PromotionRepository.cs:line 56 at MyApp.DAO.Implementations.LoanRepository.Get(Int32 loanId) in d:\a\1\s\MyApp\MyApp.DAO\Implementations\LoanRepository.cs:line 204 at MyApp.DAO.Implementations.LoanRepository.Get(String loanGuid) in d:\a\1\s\MyApp\MyApp.DAO\Implementations\LoanRepository.cs:line 0 at MyApp.BL.Los.MyManager.d__22.MoveNext() in d:\a\1\s\MyApp\MyApp.BL.Los\MyManager.cs:line 63

Ninject Configuration

 public class NinjectBindings : NinjectModule
    {
        public override void Load()
        {
            Bind<MyDBContext>().ToSelf().InSingletonScope().WithConstructorArgument("connectionString", "name=MyDB");
        }
    }

Azure Function

[FunctionName("ProcessData")]
public static async Task ProcessData([QueueTrigger("myqueue", Connection = "AzureWebJobsStorage")]string message, int dequeueCount, ILogger log, ExecutionContext context)
{
   using (StandardKernel _kernal = new StandardKernel())
   {
       _kernal.Load(Assembly.GetExecutingAssembly());
    // do work
   }
}
like image 348
Cam Bruce Avatar asked Dec 24 '17 18:12

Cam Bruce


2 Answers

Based on your description, I used your code and found that the following code could work as expected.

using (StandardKernel _kernal = new StandardKernel())
{
    _kernal.Load(Assembly.GetExecutingAssembly());

    // do work
    BruceDbContext ctx = _kernal.Get<BruceDbContext>();
    var todoitem = ctx.TodoItems.FirstOrDefault();
    log.Info(JsonConvert.SerializeObject(todoitem));
}

The ObjectContext instance has been disposed and can no longer be used for operations that require a connection. System.Data.Entity.Core.Common.Internal.Materialization.Shaper'1.SimpleEnumerator.Dispose() at System.Data.Entity.Internal.LazyEnumerator`1.Dispose()

I assumed that the errors are thrown from your operations when using EF. You need to make sure access the lazy-loading navigation properties before you dispose the DbContext. Here is a similar issue, you could refer to it. In general, you need to check your code and try to find the specific code line which causes this issue based on the full StackTrace of the exception. Or you could update your question with more details about the error and the code you used for us to narrow this issue.

Additionally, Azure Functions do not support DI that is similar to the way for webjobs. Also, I found the github issue. Moreover, you could follow Proper Dependency injection in Azure Functions on function level with scoped services! and Dependency injection in Azure Functions on function level.

like image 53
Bruce Chen Avatar answered Oct 25 '22 08:10

Bruce Chen


I'm guessing you called the context outside the using statement. The life of the context object you create using the kernel is only for the lifespan of the using statement. However,you really don't need DI in the example you provided. If you are instantiating the Kernel inside the function and then accessing the context from that why not just create a context instead. If your set on this then you need to pass the Kernal as a parameter of the function.

like image 37
Spyder045 Avatar answered Oct 25 '22 06:10

Spyder045