Scoping Entity Framework using Ninject in Azure Functions
Asked Answered
A

2

6

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
   }
}
Alum answered 24/12, 2017 at 18:26 Comment(6)
You don't know whether the context is being shared and you are using InSingletonScope? Do you even know what a Singleton is?Illinois
Of course i know what a singleton is. Ninject states: "a singleton instance is Disposed when the Kernel is Disposed", which you can clearly see, is what i am doingAlum
Do you properly await all calls inside your using? Can you provide full stack trace?Serle
Yes, all calls should be properly awaited. Furthermore, all EF code is synchronous calls.Alum
EF is not thread safe, create a single context per thread: #4456134Nagging
You want to give your container some scope, which can be done here so it is per thread, per request, etc, #15050083Unassuming
N
3

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.

Noreennorene answered 26/12, 2017 at 7:58 Comment(3)
The EF code is existing code that has been in use for quite a while, with no similar errors. I believe this error is happening because of the mis-configuration of ninject in Azure Functions. I've definitely seen the posts about DI being unsupported, but wanted to see if there is any sort of workaround - as my classes are all designed with construction injection, and i don't want to modify themAlum
@CamBruce, If your code has been in use for quite a while, than when did these troubles begin?Collogue
The trouble began when the existing data access components were used in the context of an Azure Function, versus them being used in a web application previouslyAlum
A
1

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.

Adriaadriaens answered 9/1, 2018 at 18:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.