目录
储存库模式101
撤消存储库模式
进入:LINQPad.ABP
有没有在生产中发现代码在规模上失败得很惨?当性能问题出现,并且有一个名字叫EntityFramework时,只有一个工具可用解决此问题:LINQPad。
但是,如果您使用的是ASP.NET Boilerplate(ABP)框架,那么情况就更糟了。这是因为ABP使用存储库模式,事实证明,该存储库模式与LINQPad的DataContext中心方法不兼容。
在本文中,我将介绍两种将LINQPad与ABP应用程序结合使用来解决性能问题的方法:
- 重写基于存储库模式的查询,以在具有数据上下文的LINQPad中运行。这适用于小问题。
- 借助LINQPad,我刚刚发布的名为LINQPad.ABP的NuGet Packge可以直接调用您的代码,支持身份验证,多租户以及工作单元和存储库模式。这极大地有助于解决更复杂的性能问题。
ASP.NET Boilerplate应用程序中使用的“存储库”和“工作单元”模式是一种出色的抽象,可简化单元测试,启用基于注释的事务以及处理数据库连接管理。如Microsoft MVC 文档中的本文所述:
存储库和工作单元模式旨在在应用程序的数据访问层和业务逻辑层之间创建一个抽象层。实施这些模式可以帮助您的应用程序与数据存储中的更改隔离开来,并可以促进自动化的单元测试或测试驱动的开发(TDD)。
该文档提供了一个很好的图表,以显示具有和没有存储库模式的体系结构之间的区别:
但是,这些抽象为LINQPad带来了问题。当使用LINQPad诊断性能问题时,直接调用应用程序的代码以查看将查询转换为SQL并执行查询通常会非常方便。但是,即使LINQPad理解了依赖注入,也不知道如何填充IRepository,如何处理[UnitOfWork(TransactionScopeOption.RequiresNew)]属性或为IAbpSession.UserId或IAbpSession.TenantId返回什么值。幸运的是,我刚刚发布了一个NuGet软件包以使其变得容易。
但是首先,解决单个查询问题的最简单方法就是重写没有存储库模式的查询并将其粘贴到LINQPad中。
撤消存储库模式第一步是使ABP的数据上下文支持采用连接字符串的构造函数。如果将以下代码添加到你的DataContext中:
#if DEBUG
private string _connectionString;
///
/// For LINQPad
///
public MyProjDbContext(string connectionString)
: base(new DbContextOptions())
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (_connectionString == null)
{
base.OnConfiguring(optionsBuilder); // Normal operation
return;
}
// We have a connection string
var dbContextOptionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlServer(_connectionString);
base.OnConfiguring(dbContextOptionsBuilder);
}
#endif
然后在LINQPad中,您可以:
- 添加连接
- “使用您自己的程序集中的类型化数据上下文”
- 选择自定义程序集的路径,例如“MyProjSolution\server\src\MyProj.Web.Host\bin\Debug\netcoreapp2.1\MyProj.EntityFrameworkCore.dll”
- 输入完整DbContext类型名称,如“MyProj.EntityFrameworkCore.MyAppDbContext”
- LINQPad应该通过接受字符串的构造函数实例化您的DbContext代码,然后提供您的连接字符串
现在,当您开始一个新查询时,您可以编写:
var thing = this.Things.Where(t => t.Id == 1);
thing.Dump();
而且,如果运行它,您将看到生成的SQL语句。
不错。如果粘贴任何真实代码,则需要添加using语句并替换_thingRepository.GetAll()为this.Things,并且您将立即将LINQ转换为SQL。
它当然适用于简单的查询。
但是,以我的经验,性能问题很少出现在系统的简单部分。性能问题似乎总是发生在多个类相互作用的地方,因为对于作者来说,将所有这些都塞进一个类并能够在晚上入睡根本就没有太多逻辑。
进入:LINQPad.ABP为了使LINQPad能够直接调用ABP代码,您需要设置依赖项注入,定义一个启动核心模块的模块,指定要模拟的当前用户和租户,并以某种方式覆盖默认的工作单元以使用LINQPad的上下文,而不是ABP的上下文。这是很多工作。
幸运的是,我刚刚发布了LINQPad.ABP,这是一个为您完成所有这些工作的开源NuGet包。要启用它:
- 在LINQPad中,添加对“LINQPad.ABP”的引用。
- 添加一个依赖于您项目的特定EF模块的自定义模块。
- 创建并缓存LINQPad ABP上下文。
- 启动UnitOfWork并指定您要模拟的用户和租户。
该代码将如下所示:
// you may need to depend on additional modules here e.g., MyProjApplicationModule
[DependsOn(typeof(MyProjEntityFrameworkModule))]
// this is a lightweight custom module just for LINQPad
public class LinqPadModule : LinqPadModuleBase
{
public LinqPadModule(MyProjEntityFrameworkModule abpProjectNameEntityFrameworkModule)
{
// tell your project's EF module to refrain from seeding the DB
abpProjectNameEntityFrameworkModule.SkipDbSeed = true;
}
public override void InitializeServices(ServiceCollection services)
{
// add any custom dependency injection registrations here
IdentityRegistrar.Register(services);
}
}
async Task Main()
{
// LINQPad.ABP caches (expensive) module creation in LINQPad's cache
var abpCtx = Util.Cache(LinqPadAbp.InitModule(), "LinqPadAbp");
// specify the tenant or user you want to impersonate here
using (var uowManager = abpCtx.StartUow(this, tenantId: 5, userId: 8))
{
// retrieve what you need with IocManager in LINQPad.ABP's context
var thingService = abpCtx.IocManager.Resolve();
var entity = await thingService.GetEntityByIdAsync(1045);
entity.Dump();
}
}
那可能看起来很多代码,但是请相信我,它比以前要简单得多。现在,您可以调用代码并观看每个查询以及如何将查询转换为SQL。我希望这可用更快的帮助你跟踪到困难的bug。