具有MVC,Ninject和WCF服务的控制台应用程序(Dispose issue?)

时间:2014-11-27 11:39:38

标签: c# wcf asp.net-mvc-4 dependency-injection ninject

我有一个MVC应用程序,所有Ninject内容都已正确连接。在应用程序中,我想添加调用WCF服务的功能,然后WCF服务将批量消息(即批量打印)发送到RabbitMQ队列。

A'处理器' app订阅队列中的消息并处理它们。这也是我想要更新数据库中的一些内容的地方,所以我希望我的MVC应用程序中的所有服务和存储库也可用。

处理器应用实现以下功能:

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;
            }
        }
    }
}

所有Ninject存储库绑定都在RepositoryModule中指定,它也在MVC应用程序中使用,如下所示:

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

处理器类

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
    }
}

一切正常,直到我直接从MVC应用程序或数据库更新数据库。处理器应用程序对这些更改一无所知,并且在下次尝试处理某些更改时,它会在“缓存”上运行。的DbContext。我确定它与未正确处理DbContext有关,但我不确定应该为控制台应用程序使用什么范围(尝试各种不同的范围无济于事)。

我现在能想到的唯一解决方案是从处理器应用程序中调用WCF服务并在服务中执行所有必要的更新,但我希望避免这种情况。

更新:添加更新逻辑

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);
    }
}

ReviewService中的UpdateReview方法:

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);
          }
      }
}

上下文本身就是这样绑定的:

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

从范围的描述中我认为Transient范围是正确的选择,但正如我之前所说,我尝试了各种类型,包括RequestScope,TransientScope,NamedScope甚至Singleton(尽管我知道它不是理想的行为) ,但他们似乎都没有正确处理上下文。

1 个答案:

答案 0 :(得分:0)

您需要的是每个交易一个DbContext个实例。 现在其他&#34;应用程序&#34;像web应用程序或wcf-service可能每个请求都做一个事务(因此使用InRequestScope()之类的东西。还要注意,这些应用程序为每个请求创建一个对象图。但是,这是一个你不知道的概念。控制台应用程序。

此外,范围仅影响对象的实例化。一旦它们被实例化,范围确定对它们没有任何影响。

因此解决问题的一种方法是为每个事务创建(相关)对象树/图,然后你可以使用InCallScope()(InCallScope真正意味着&#34;每次实例化一个对象图&#34 34;,见here)。 这意味着您需要IReviewPrintMessage的工厂(查看ninject.extensions.factory)并在每次要执行IReviewPrintMessage时创建IReviewPrintMessage.ProcessReviewPrint的实例

现在您已经重新创建了&#34;每个请求模式&#34;。

但是,关于CompositionRoot,建议不要这样做。

替代方案:您也可以根据需要重新创建DbContext。不是在任何地方传递它(DbContext作为几乎每种方法的附加参数)而是使用SynchronizationContext本地存储(或者如果您不使用TPL / async await:a { {1}})。我已经更详细地描述了这种方法here