0

I have a MVC application with all Ninject stuff wired up properly. Within the application I wanted to add functionality to call a WCF service, which then sends bulk messages (i.e. bulk printing) to RabbitMQ queue .

A 'processor' app subscribes to messages in the queue and process them. This is where I also want to update some stuff in the database, so I want all my services and repositories from the MVC app to be available too.

The processor app implements the following:

public abstract class KernelImplementation
{
    private IKernel _kernel;

    public IKernel Kernel
    {
        get
        {
            if (_kernel != null)
                return _kernel;
            else
            {
                _kernel = new StandardKernel(new RepositoryModule(),
                                                 new DomainModule(),
                                                 new ServiceModule(),
                                                 new MessageModule());
                return _kernel;
            }
        }
    }
}

All Ninject repository bindings are specified within RepositoryModule, which is also used within MVC app and look like this:

Bind<IReviewRepository>().To<ReviewRepository>().InCallScope();

The processor class

public class Processor : KernelImplementation
{
    private readonly IReviewPrintMessage _reviewPrintMessage;

    public Processor()
    {
        _reviewPrintMessage = Kernel.Get<IReviewPrintMessage>();

        [...]

        _bus.Subscribe<ReviewPrintContract>("ReviewPrint_Id",
                (reviewPrintContract) => _reviewPrintMessage.ProcessReviewPrint(reviewPrintContract));
        //calling ProcessReviewPrint where I want my repositories to be available
    }
}

Everything works fine until I update the database from the MVC app or database directly. The processor app doesn't know anything about those changes and the next time it tries to process something, it works on a 'cached' DbContext. I'm sure it's something to do with not disposing the DbContext properly, but I'm not sure what scope should be used for a console app (tried all sort of different scopes to no avail).

The only solution I can think of at the moment is to call WCF service back from the processor app and perform all the necessary updates within the service, but I would want to avoid that.

UPDATE: Adding update logic

Simplified ReviewPrintMessage:

public class ReviewPrintMessage : IReviewPrintMessage
{
    private readonly IReviewService _reviewService;

    public ReviewPrintMessage(IReviewService reviewService)
    {
        _reviewService = reviewService;
    }

    public void ProcessReviewPrint(ReviewPrintContract reviewPrintContract)
    {
        var review =
            _reviewService.GetReview(reviewPrintContract.ReviewId);

        [...]
        //do all sorts of stuff here
        [...]
        _reviewService.UpdateReview(review);
    }
}

UpdateReview method in ReviewService:

public void UpdateTenancyAgreementReview(TenancyAgreementReview review)
{
    _tenancyAgreementReviewRepository.Update(review);
    _unitOfWork.Commit();
}

RepositoryBase:

public abstract class EntityRepositoryBase<T> where T : class
{
     protected MyContext _dataContext;

     protected EntityRepositoryBase(IDbFactory dbFactory)
     {
          this.DbFactory = dbFactory;
          _dbSet = this.DataContext.Set<T>();
     }

     [...]

     public virtual void Update(T entity)
     {
          try
          {
               DataContext.Entry(entity).State = EntityState.Modified;
          }
          catch (Exception exception)
          {
               throw new EntityException(string.Format("Failed to update entity '{0}'", typeof(T).Name), exception);
          }
      }
}

Context itself is bound like this:

Bind<MyContext>().ToSelf().InCallScope();

From the description of scopes I thought that Transient scope was the right choice, but as I said earlier I tried all sorts including RequestScope, TransientScope, NamedScope and even Singleton (although I knew it wouldn't be desired behaviour), but none of them seem to be disposing the context properly.

Jerry
  • 1,762
  • 5
  • 28
  • 42
  • please post the code to update and process. – Florian Nov 27 '14 at 11:47
  • I think you want a `Transient` scope, you need to get a new instance every time. You have not posted your code where you are doing the update, but the fact that it is reusing an instance suggests you are not properly disposing of the context either otherwise you would be getting `ObjectDisposedException`. – Ben Robinson Nov 27 '14 at 11:47

1 Answers1

0

What you'll need is one DbContext instance per transaction. Now other "applications" like web-applications or wcf-service may be doing one transaction per request (and thus use something like InRequestScope(). Also note, that these application create an object graph for each request. However, that is a concept unknown to your console application.

Furthermore, scoping only affects the instantiation of objects. Once they are instantiated, Scoping does not have any effect on them.

So one way to solve your issue would be to create the (relevant) object tree/graph per transaction and then you could use InCallScope() (InCallScope really means "once per instantiation of an object graph", see here). That would mean that you'd need a factory for IReviewPrintMessage (have a look at ninject.extensions.factory) and create an instance of IReviewPrintMessage every time you want to execute IReviewPrintMessage.ProcessReviewPrint.

Now you have re-created the "per request pattern".

However, regarding CompositionRoot this is not recommended.

Alternative: you can also only re-create the DbContext as needed. Instead of passing it around everywhere (DbContext as additional parameter on almost every method) you use a SynchronizationContext local storage (or if you don't use TPL/async await: a ThreadLocal). I've already described this method in more detail here

Community
  • 1
  • 1
BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • I have tried using a factory with Wcf.Extensions.Factory to no avail. I believe there might be an underlying problem with how my DbContext/UnitOfWork is implemented. Thanks for suggesting an alternative, which I will need to try at some point. Currently due to lack of time I had to abandon the idea of using a console app and perform all the operations within the WCF service. – Jerry Nov 29 '14 at 18:46