解决ASP.NET MVC中的LINQ查询性能问题

加入新表后,我遇到LINQ查询问题。 实际上,它返回我期望的数据,并且在测试中运行得很快。 但是,似乎随着越来越多的用户连接到数据库,查询开始超时。 例如,在生产中的前30或45分钟内一切正常,但是在大约上午8:20,它开始超时。 同样,我认为这是由于整体上数据库的使用增加。

以下是ASP.NET MVC(5)应用程序的一些背景知识,如果有帮助的话。

  • 用户向我们的诊所提交转诊
  • 推荐包含一个或多个订单
  • 如果提供的人员信息与现有人员不匹配,我会做几件事,包括在“订单”表中插入记录(在推荐中选择的每个订单的一个记录)。
  • 如果提供的人员信息与我们系统中的现有人员匹配,那么我将“推荐”保留在队列中,直到通过将其与现有人员匹配或通过覆盖并在系统中创建新人员来手动解决。 此时,在表格中创建在引荐中选择的任何订单。

因此,在这种情况下要考虑的两个主要表是“推荐”(在我的代码中命名为“Referrals”)和“order”(在我的代码中命名为“ReferralPPAs”)表。 到目前为止,我还不需要将有问题的查询从Referrals表链接到ReferralPPAs表(将查询链接到ReferralPPAs表似乎是在数据库/应用程序使用增加时减慢查询的速度)。

此外,如果这有帮助,推荐由外部用户输入,而我从推荐创建的订单在一个单独的应用程序中工作,内部人员作为用户,尽管它们都在同一个数据库中。 ReferralPPAs表可能在一天中的大部分时间都被大量使用。

查询如下所示:

IQueryable referrals = (from r in _context.Referrals join cu in _context.ClinicUsers on r.ClinicId equals cu.ClinicId /* Here is the seemingly problematic join */ from ppa in _context.ReferralPPAs .Where(p => p.ref_id == r.seq_no.ToString()) .DefaultIfEmpty() /* End of seemingly problematic join */ join ec in _context.EnrolledClinics on r.ClinicId equals ec.ClinicId join pm in _context.ProviderMasters on ec.ClinicId equals pm.ClinicId join ml in _context.MasterLists on pm.HealthSystemGuid equals ml.Id join au in _context.Users on r.ApplicationUserId equals au.Id where cu.UserId == userId select new ReferralListViewModel() { ClinicName = pm.Description, ClinicId = r.ClinicId, ReferralId = r.seq_no, EnteredBy = (au.FirstName ?? string.Empty) + " " + (au.LastName ?? string.Empty), PatientName = (r.LastName ?? string.Empty) + ", " + (r.FirstName ?? string.Empty), DateEntered = r.create_timestamp, Status = ppa != null ? ppa.Status : string.Empty }); 

所以,没有我参考上面的连接,我没有遇到任何问题,它运行得非常快。 添加连接似乎也很快,直到系统上有一定数量的用户(至少这是我的假设)。

我试图帮助提高性能并防止出现问题。 我将UseDatabaseNullSemantics设置为True,这似乎对整体性能产生很大影响。

 _context.Configuration.UseDatabaseNullSemantics = true; 

我也想知道问题是否是锁定在有问题的表上的问题,所以我尝试在事务中包装查询来执行ReadUncommitted。

  using (var transaction = _context.Database.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted)) { //query } 

同样,虽然这会稍微改善整体性能,但似乎并没有最终解决问题。

如果有人对如何解决这个问题有任何想法,想法或建议,我将不胜感激。

根据注释中的其他信息,看起来像连接条件中的Guid to String转换

 p.ref_id == r.seq_no.ToString() 

翻译成

 t1.ref_id = LOWER(CAST(t2.seq_no AS nvarchar(max)))) 

使得查询不是sargable,而隐式SqlServer转换

 t1.ref_id = t2.seq_no 

工作得很好。

所以问题是如何删除该演员表。 没有选项,查询表达式树也不允许删除它。 如果SqlServer提供程序sql生成器正在进行优化,那将是很好的,但它没有,并且没有简单的方法来挂钩它。

作为一种解决方法,我可以提供以下解决方案。 它使用自定义IDbCommandTreeInterceptorDbExpressionVisitor来修改查询的DbCommandTree

这是拦截代码:

 using System; using System.Data.Entity.Core.Common.CommandTrees; using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder; using System.Data.Entity.Core.Metadata.Edm; using System.Data.Entity.Infrastructure.Interception; using System.Linq.Expressions; using System.Reflection; namespace EFHacks { public class MyDbCommandTreeInterceptor : IDbCommandTreeInterceptor { public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext) { if (interceptionContext.OriginalResult.DataSpace != DataSpace.SSpace) return; var queryCommand = interceptionContext.Result as DbQueryCommandTree; if (queryCommand != null) { var newQuery = queryCommand.Query.Accept(new GuidToStringComparisonRewriter()); if (newQuery != queryCommand.Query) { interceptionContext.Result = new DbQueryCommandTree( queryCommand.MetadataWorkspace, queryCommand.DataSpace, newQuery); } } } } class GuidToStringComparisonRewriter : DefaultExpressionVisitor { public override DbExpression Visit(DbComparisonExpression expression) { if (IsString(expression.Left.ResultType) && IsString(expression.Right.ResultType)) { var left = expression.Left; var right = expression.Right; if (RemoveCast(ref right) || RemoveCast(ref left)) return CreateComparison(expression.ExpressionKind, left, right); } return base.Visit(expression); } static bool IsGuid(TypeUsage type) { var pt = type.EdmType as PrimitiveType; return pt != null && pt.PrimitiveTypeKind == PrimitiveTypeKind.Guid; } static bool IsString(TypeUsage type) { var pt = type.EdmType as PrimitiveType; return pt != null && pt.PrimitiveTypeKind == PrimitiveTypeKind.String; } static bool RemoveCast(ref DbExpression expr) { var funcExpr = expr as DbFunctionExpression; if (funcExpr != null && funcExpr.Function.BuiltInTypeKind == BuiltInTypeKind.EdmFunction && funcExpr.Function.FullName == "Edm.ToLower" && funcExpr.Arguments.Count == 1) { var castExpr = funcExpr.Arguments[0] as DbCastExpression; if (castExpr != null && IsGuid(castExpr.Argument.ResultType)) { expr = castExpr.Argument; return true; } } return false; } static readonly Func CreateComparison = BuildCreateComparisonFunc(); static Func BuildCreateComparisonFunc() { var kind = Expression.Parameter(typeof(DbExpressionKind), "kind"); var booleanResultType = Expression.Field(null, typeof(DbExpressionBuilder), "_booleanType"); var left = Expression.Parameter(typeof(DbExpression), "left"); var right = Expression.Parameter(typeof(DbExpression), "right"); var result = Expression.New( typeof(DbComparisonExpression).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { kind.Type, booleanResultType.Type, left.Type, right.Type }, null), kind, booleanResultType, left, right); var expr = Expression.Lambda>( result, kind, left, right); return expr.Compile(); } } } 

DbConfiguration安装它:

 class MyDbConfiguration : DbConfiguration { public MyDbConfiguration() { AddInterceptor(new EFHacks.MyDbCommandTreeInterceptor()); } } 

使用SqlServer数据库在EF6.1.3和EF6.2中进行测试和工作。

但要小心使用它。

首先,它仅适用于SqlServer。

其次,它是hackish,因为我必须使用内部字段和内部类构造函数,以跳过检查相同类型的比较操作操作数。 因此,未来的EF6更新可能会破坏它。