Ninject、MVC5、EF6、存储库 + UoW
Ninject, MVC5, EF6, Repository + UoW
我有两个问题,第一个是缩放并且在负载测试时变得可见。
在负载下,事情很快(10 个并发或更少)失败并出现错误:
There is already an open DataReader associated with this Command which must be closed first.
或
ExecuteReader requires an open and available Connection. The connection's current state is open.
堆栈跟踪每次都引用一个存储库,示例:
Line 23: public AboutViewDto GetAboutView()
Line 24: {
Line 25: var featured = UnitOfWork.FaqRepository
Line 26: .GetFeatured()
原始 NinjectDependencyResolver:
public class NinjectDependencyResolver
: IDependencyResolver, System.Web.Mvc.IDependencyResolver
{
private readonly IKernel _kernel;
public NinjectDependencyResolver() : this(new StandardKernel())
{
}
public NinjectDependencyResolver(IKernel kernel)
{
_kernel = kernel;
AddBindings(_kernel);
}
public IDependencyScope BeginScope()
{
return this;
}
public object GetService(Type serviceType)
{
return _kernel.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return _kernel.GetAll(serviceType);
}
public void Dispose()
{
// nothing??
}
private static void AddBindings(IBindingRoot kernel)
{
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
kernel.Bind<IDataContext>().To<PublicCommentDbContext>().InSingletonScope();
kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InSingletonScope();
kernel.Bind(typeof(IRepository<>)).To(typeof(Repository<>)).InSingletonScope();
kernel.Bind<IFaqRepository>().To<FaqRepository>().InSingletonScope();
kernel.Bind<IListValueRepository>().To<ListValueRepository>().InSingletonScope();
kernel.Bind<INoticeRepository>().To<NoticeRepository>().InSingletonScope();
kernel.Bind<IOrganizationRepository>().To<OrganizationRepository>().InSingletonScope();
kernel.Bind<ITagRepository>().To<TagRepository>().InSingletonScope();
kernel.Bind<IAdminService>().To<AdminService>();
kernel.Bind<IAutoMapperService>().To<AutoMapperService>();
kernel.Bind<IHomeService>().To<HomeService>();
kernel.Bind<IInfoService>().To<InfoService>();
kernel.Bind<IMailService>().To<MailService>();
kernel.Bind<INoticeService>().To<NoticeService>();
kernel.Bind<IOrganizationService>().To<OrganizationService>();
kernel.Bind<ISearchService>().To<SearchService>();
kernel.Bind<IValidator<QuestionDto>>().To<QuestionDtoValidator>();
kernel.Bind<IValidator<NoticeCommentDto>>().To<CommentDtoValidator>();
kernel.Bind<IValidator<NoticeContactDto>>().To<NoticeContactDtoValidator>();
kernel.Bind<IValidator<NoticeDto>>().To<NoticeDtoValidator>();
kernel.Bind<IValidator<OrganizationDto>>().To<OrganizationDtoValidator>();
}
}
}
我有预感 InSingletonScope() 导致了问题,所以我将其更改为:
kernel.Bind<IDataContext>().To<PublicCommentDbContext>().InRequestScope();
并将所有其他 SingletonScopes 更改为 RequestScope。
进行该更改后,该站点可以无故障地处理 400 多个并发用户,但是...
现在没有对数据库的提交工作。我可以通过控制器跟踪调用、服务、存储库和 DBContext 提交,但没有发生插入或更新。
我将 post 此处列出每个项目的片段,希望有人能发现我们犯的愚蠢错误或提出改进建议。
代码段如下:
Activity,更新一条通知,涉及的一切:
1) Ninject:
kernel.Bind<IDataContext>().To<PublicCommentDbContext>().InRequestScope();
kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope();
kernel.Bind(typeof(IRepository<>)).To(typeof(Repository<>)).InRequestScope();
kernel.Bind<INoticeService>().To<NoticeService>();
.. etc...
2) 控制器:
public sealed class NoticeController : BaseController
{
public NoticeController(IAutoMapperService autoMapperService, INoticeService noticeService)
{
AutoMapperService = autoMapperService;
NoticeService = noticeService;
}
...
3) 通知服务
public class NoticeService : BaseService, INoticeService
{
public NoticeService(
IUnitOfWork unitOfWork,
IAutoMapperService autoMapperService,
IValidator<NoticeDto> noticeDtoValidator)
: base(unitOfWork)
{
AutoMapperService = autoMapperService;
NoticeDtoValidator = noticeDtoValidator;
}
4) 工作单元
public class UnitOfWork : IUnitOfWork
{
private IDataContext _dataContext;
private bool _disposed;
private ObjectContext _objectContext;
private DbTransaction _transaction;
public UnitOfWork(
IDataContext dataContext,
INoticeRepository noticeRepository)
{
_dataContext = dataContext;
NoticeRepository = noticeRepository;
}
5) 通知存储库
public class NoticeRepository : Repository<Notice>, INoticeRepository
{
public NoticeRepository(IDataContext context) : base(context)
{
}
...
6) 控制器动作
public ActionResult Create(NoticeViewModel notice)
{
notice.TypeId = Convert.ToInt32(NoticeType.ManuallyEnteredDocument);
var newNotice = AutoMapperService.Map<NoticeViewModel, NoticeDto>(notice);
NoticeService.Create(newNotice);
return RedirectToAction("Details", new { name = notice.Arc });
}
7) NoticeService.Create(新):
public void Create(NoticeDto notice)
{
NoticeDtoValidator.ValidateAndThrow(notice);
var newNotice = AutoMapperService.Map<NoticeDto, Notice>(notice);
UnitOfWork.NoticeRepository.Add(newNotice);
UnitOfWork.SaveChanges();
}
8) 通用存储库添加():
public virtual void Add(TEntity entity)
{
entity.ObjectState = ObjectState.Added;
_dbSet.Attach(entity);
_context.SyncObjectState(entity);
}
9) 工作单元保存更改():
public int SaveChanges()
{
return _dataContext.SaveChanges();
}
当我逐步执行此操作时,#8 中附加的实体看起来正确,状态已添加,当调用保存更改时,基本 SaveChanges() 中没有记录任何更改:
public override int SaveChanges()
{
SyncObjectsStatePreCommit();
base.ChangeTracker.DetectChanges();
var result = base.ChangeTracker.HasChanges();
var changes = base.SaveChanges();
这里的结果是假的
怎么可能?除了抽象地狱和 EF6 上下文之上的 UoW/Repos(我继承了它),我还缺少什么。
外面有人看到其他愚蠢的东西吗?
没有错误,没有异常,只是没有发生插入。
问题已解决,问题是 Ninject 将不同的上下文注入我们的存储库。
我们找到了两种解决方法:
选项 1) 在我们的工作单元中,我们创建了一个额外的构造函数,它只接收一个 dbcontext 并创建新的存储库,将 dbcontext 传递给它们。我们也保留了旧的构造函数,因此我们可以注入 repos 进行测试。这非常有效,并且是一个微不足道的改变。
选项 2) 删除了 Ninject 并重载了每个控制器的构造函数,手动创建项目。由于这个项目很小,我们只需要接触 6 个控制器就可以正常工作,每个控制器只有 4 行新代码。我们也保留了旧的构造函数,因此我们可以注入服务进行测试。
我有两个问题,第一个是缩放并且在负载测试时变得可见。
在负载下,事情很快(10 个并发或更少)失败并出现错误:
There is already an open DataReader associated with this Command which must be closed first.
或
ExecuteReader requires an open and available Connection. The connection's current state is open.
堆栈跟踪每次都引用一个存储库,示例:
Line 23: public AboutViewDto GetAboutView()
Line 24: {
Line 25: var featured = UnitOfWork.FaqRepository
Line 26: .GetFeatured()
原始 NinjectDependencyResolver:
public class NinjectDependencyResolver
: IDependencyResolver, System.Web.Mvc.IDependencyResolver
{
private readonly IKernel _kernel;
public NinjectDependencyResolver() : this(new StandardKernel())
{
}
public NinjectDependencyResolver(IKernel kernel)
{
_kernel = kernel;
AddBindings(_kernel);
}
public IDependencyScope BeginScope()
{
return this;
}
public object GetService(Type serviceType)
{
return _kernel.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return _kernel.GetAll(serviceType);
}
public void Dispose()
{
// nothing??
}
private static void AddBindings(IBindingRoot kernel)
{
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
kernel.Bind<IDataContext>().To<PublicCommentDbContext>().InSingletonScope();
kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InSingletonScope();
kernel.Bind(typeof(IRepository<>)).To(typeof(Repository<>)).InSingletonScope();
kernel.Bind<IFaqRepository>().To<FaqRepository>().InSingletonScope();
kernel.Bind<IListValueRepository>().To<ListValueRepository>().InSingletonScope();
kernel.Bind<INoticeRepository>().To<NoticeRepository>().InSingletonScope();
kernel.Bind<IOrganizationRepository>().To<OrganizationRepository>().InSingletonScope();
kernel.Bind<ITagRepository>().To<TagRepository>().InSingletonScope();
kernel.Bind<IAdminService>().To<AdminService>();
kernel.Bind<IAutoMapperService>().To<AutoMapperService>();
kernel.Bind<IHomeService>().To<HomeService>();
kernel.Bind<IInfoService>().To<InfoService>();
kernel.Bind<IMailService>().To<MailService>();
kernel.Bind<INoticeService>().To<NoticeService>();
kernel.Bind<IOrganizationService>().To<OrganizationService>();
kernel.Bind<ISearchService>().To<SearchService>();
kernel.Bind<IValidator<QuestionDto>>().To<QuestionDtoValidator>();
kernel.Bind<IValidator<NoticeCommentDto>>().To<CommentDtoValidator>();
kernel.Bind<IValidator<NoticeContactDto>>().To<NoticeContactDtoValidator>();
kernel.Bind<IValidator<NoticeDto>>().To<NoticeDtoValidator>();
kernel.Bind<IValidator<OrganizationDto>>().To<OrganizationDtoValidator>();
}
}
}
我有预感 InSingletonScope() 导致了问题,所以我将其更改为:
kernel.Bind<IDataContext>().To<PublicCommentDbContext>().InRequestScope();
并将所有其他 SingletonScopes 更改为 RequestScope。
进行该更改后,该站点可以无故障地处理 400 多个并发用户,但是...
现在没有对数据库的提交工作。我可以通过控制器跟踪调用、服务、存储库和 DBContext 提交,但没有发生插入或更新。
我将 post 此处列出每个项目的片段,希望有人能发现我们犯的愚蠢错误或提出改进建议。
代码段如下:
Activity,更新一条通知,涉及的一切:
1) Ninject:
kernel.Bind<IDataContext>().To<PublicCommentDbContext>().InRequestScope();
kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope();
kernel.Bind(typeof(IRepository<>)).To(typeof(Repository<>)).InRequestScope();
kernel.Bind<INoticeService>().To<NoticeService>();
.. etc...
2) 控制器:
public sealed class NoticeController : BaseController
{
public NoticeController(IAutoMapperService autoMapperService, INoticeService noticeService)
{
AutoMapperService = autoMapperService;
NoticeService = noticeService;
}
...
3) 通知服务
public class NoticeService : BaseService, INoticeService
{
public NoticeService(
IUnitOfWork unitOfWork,
IAutoMapperService autoMapperService,
IValidator<NoticeDto> noticeDtoValidator)
: base(unitOfWork)
{
AutoMapperService = autoMapperService;
NoticeDtoValidator = noticeDtoValidator;
}
4) 工作单元
public class UnitOfWork : IUnitOfWork
{
private IDataContext _dataContext;
private bool _disposed;
private ObjectContext _objectContext;
private DbTransaction _transaction;
public UnitOfWork(
IDataContext dataContext,
INoticeRepository noticeRepository)
{
_dataContext = dataContext;
NoticeRepository = noticeRepository;
}
5) 通知存储库
public class NoticeRepository : Repository<Notice>, INoticeRepository
{
public NoticeRepository(IDataContext context) : base(context)
{
}
...
6) 控制器动作
public ActionResult Create(NoticeViewModel notice)
{
notice.TypeId = Convert.ToInt32(NoticeType.ManuallyEnteredDocument);
var newNotice = AutoMapperService.Map<NoticeViewModel, NoticeDto>(notice);
NoticeService.Create(newNotice);
return RedirectToAction("Details", new { name = notice.Arc });
}
7) NoticeService.Create(新):
public void Create(NoticeDto notice)
{
NoticeDtoValidator.ValidateAndThrow(notice);
var newNotice = AutoMapperService.Map<NoticeDto, Notice>(notice);
UnitOfWork.NoticeRepository.Add(newNotice);
UnitOfWork.SaveChanges();
}
8) 通用存储库添加():
public virtual void Add(TEntity entity)
{
entity.ObjectState = ObjectState.Added;
_dbSet.Attach(entity);
_context.SyncObjectState(entity);
}
9) 工作单元保存更改():
public int SaveChanges()
{
return _dataContext.SaveChanges();
}
当我逐步执行此操作时,#8 中附加的实体看起来正确,状态已添加,当调用保存更改时,基本 SaveChanges() 中没有记录任何更改:
public override int SaveChanges()
{
SyncObjectsStatePreCommit();
base.ChangeTracker.DetectChanges();
var result = base.ChangeTracker.HasChanges();
var changes = base.SaveChanges();
这里的结果是假的
怎么可能?除了抽象地狱和 EF6 上下文之上的 UoW/Repos(我继承了它),我还缺少什么。
外面有人看到其他愚蠢的东西吗?
没有错误,没有异常,只是没有发生插入。
问题已解决,问题是 Ninject 将不同的上下文注入我们的存储库。
我们找到了两种解决方法:
选项 1) 在我们的工作单元中,我们创建了一个额外的构造函数,它只接收一个 dbcontext 并创建新的存储库,将 dbcontext 传递给它们。我们也保留了旧的构造函数,因此我们可以注入 repos 进行测试。这非常有效,并且是一个微不足道的改变。
选项 2) 删除了 Ninject 并重载了每个控制器的构造函数,手动创建项目。由于这个项目很小,我们只需要接触 6 个控制器就可以正常工作,每个控制器只有 4 行新代码。我们也保留了旧的构造函数,因此我们可以注入服务进行测试。