在 AngularJs 和 C# 中使用 Linq 查询搜索大量记录的最快方法

Fastest way to search huge records using Linq query in AngularJs & C#

我必须在 table 上执行全局搜索意味着如果用户输入任何关键字或多个关键字并单击搜索按钮然后根据输入的关键字它应该带来所有组合记录。

我们必须在 table 的每一列中搜索这 2 个关键字(SQL 中的 Like 子句对多个关键字使用 OR 运算符)并且查询应该获取数据。

我在数据库中有大约 20 万条记录。

第一次调用函数加载数据

if ((Role)user.Role == Role.InternalAdministrator || (Role)user.Role == 
Role.InternalStaff)
{
listJobs = (
from d in db.Jobs
where d.TimeCreated.Value.Year >= 2020
select new JobModel()
{
AlternatePickupDelivery = d.AlternatePickupDelivery,
Branch = (
from b in db.Branches
where b.BranchId == d.ProcessingCity
select b.Branch1
).FirstOrDefault(),
ClientName = d.ClientName,
ClientId = d.ClientId,
ContactName = d.ContactName,
MatterReference = d.MatterReference,
JMSNumber = d.JmsNumber,
JobDescription = d.JobDescription,
JobId = d.JobId,
JobShortDescription = d.JobShortDescription,
OrderType = d.OrderType,
OrderTypeDisplay = (
from jt in db.JobTypes
where jt.Id == d.OrderType
select jt.JobTypeName
).FirstOrDefault(),
ProcessingCity = d.ProcessingCity ?? 0,
DisplayProcessingCity = (
from jt in db.ProcessingCities
where jt.ProcessingCityId == d.ProcessingCity
select jt.ProcessingCity1
).FirstOrDefault(),
Status = d.Status,
DisplayStatus = (
from jt in db.JobStatuses
where jt.Id == d.Status
select jt.JobStatusName
).FirstOrDefault(),
StatusDisplayOrder = (from js in db.JobStatuses
where js.Id == d.Status
select js.DisplayOrder).FirstOrDefault(),//d.JobStatus.DisplayOrder,
StatusLastModifiedBy = (
from u in db.Users
where (u.UserId == d.StatusLastModifiedById)
select u.FirstName + " " + u.LastName
).FirstOrDefault(),
StatusLastModifiedById = d.StatusLastModifiedById,
StatusLastModified = d.StatusLastModified ?? DateTime.UtcNow,
CreatedByDisplay = (
from u in db.Users
where (u.UserId == d.CreatedById)
select u.FirstName + " " + u.LastName
).FirstOrDefault(),
CreatedById = d.CreatedById,
ModifiedByDisplay = (
from u in db.Users
where (u.UserId == d.LastModifiedById)
select u.FirstName + " " + u.LastName
).FirstOrDefault(),
LastModifiedById = d.LastModifiedById,
TimeCreated = d.TimeCreated ?? DateTime.UtcNow,
TimeDelivered = (d.Status == (int)JMS4.Utilities.JobStatus.Delivered) ? d.StatusLastModified : 
null,
TimeDue = d.TimeDue ?? DateTime.UtcNow,
TimeReady = d.TimeReady ?? DateTime.UtcNow,
TimeZoneId = timeZoneId.ToString(),
ExtClientId = d.ExtClientId,
Address = d.Address,
ReceivedBy = d.ReceivedBy,
ContactPhone = d.ContactPhone,
AfterHourContactNumber = d.AfterHoursContactNumber,
Email = d.Email,
CostEstimateNumber = d.CostEstimateNumber,
LastModifiedBy = d.LastModifiedBy,
MatterType = d.MatterType,
QaData = d.QaData,
InternalInstructions = d.InternalInstructions,
GlobalSearch = d.GlobalSearch
}
);

如果搜索文本框有任何 keyword/keywords 要搜索

,则调用第二个函数
jobs = jobs.Where(x => x.JMSNumber.ToLower().Contains(keyword.ToLower())
|| (x.ClientName != null && x.ClientName.ToLower().Contains(keyword.ToLower()))
|| (x.MatterReference != null && x.MatterReference.ToLower().Contains(keyword.ToLower()))
|| (x.ContactName != null && x.ContactName.ToLower().Contains(keyword.ToLower()))
|| (x.JobShortDescription != null && 
x.JobShortDescription.ToLower().Contains(keyword.ToLower()))
|| (x.StatusLastModifiedBy != null && 
x.StatusLastModifiedBy.ToLower().Contains(keyword.ToLower()))
|| (x.Address != null && x.Address.ToLower().Contains(keyword.ToLower()))
|| (x.Email != null && x.Email.ToLower().Contains(keyword.ToLower()))
|| (x.LastModifiedBy != null && 
x.LastModifiedBy.ToString().ToLower().Contains(keyword.ToLower()))
|| (x.CostEstimateNumber != null && 
x.CostEstimateNumber.ToLower().Contains(keyword.ToLower()))
|| (x.ClientId != null && x.ClientId.ToString().ToLower().Contains(keyword.ToLower()))
|| (x.JobDescription != null && !String.IsNullOrEmpty(x.JobDescription.ToString()) && 
x.JobDescription.ToString().ToLower().Contains(keyword.ToLower()))
|| (x.CreatedByDisplay != null && !String.IsNullOrEmpty(x.CreatedByDisplay.ToString()) && 
x.CreatedByDisplay.ToString().ToLower().Contains(keyword.ToLower()))
|| (x.ModifiedByDisplay != null && !String.IsNullOrEmpty(x.ModifiedByDisplay.ToString()) && 
x.ModifiedByDisplay.ToString().ToLower().Contains(keyword.ToLower()))
|| (x.InternalInstructions != null && 
x.InternalInstructions.ToString().ToLower().Contains(keyword.ToLower()))
);

使用这些查询后,获取记录需要 3 多分钟。

请建议我如何提高搜索性能和优化查询。

要针对数据库优化这样的查询,可以尝试并遵循一些规则

  • 确保查询传递到数据库,不要在内存中操作
  • 删除或减少函数的使用
  • 不用比较空值
  • 将查询拆分为多个并行查询
  • 改进结构以优化查询

一般的想法是你想直接在索引条目中进行比较,函数或数据库中记录的转换不会使用索引。数据库专门针对索引查询进行了优化,因此在搜索列上创建必要的索引也很重要。

You have tagged this as so we assume that your query is being passed through to the DB, it is important that you make sure it does. The following code and advice will only work if the LINQ expression is a genuine IQueryable<T> that will be resolved into SQL.

  1. 如果您的数据库使用 CASE INSENSITIVE 排序规则,那么您可以删除所有 .ToLower()函数调用,您希望避免函数调用以便可以直接访问任何索引。

    • 尽管 C# 默认情况下 对大小写敏感 ,但如果 LINQ 查询转换为 SQL,那么它将遵循标准 LIKE '%' + @param + '%' 的排序规则设置]比较。
  2. 跳过 null 比较,就像 .ToLower() 在 SQL 中没有必要在对该字段执行比较之前先检查该字段的可空性。

这已经是一个更好的过滤器:

jobs = jobs.Where(x => x.JMSNumber.Contains(keyword)
                    || x.ClientName.Contains(keyword)
                    || x.MatterReference.Contains(keyword)
                    || x.ContactName.Contains(keyword)
                    || x.JobShortDescription.Contains(keyword)
                    || x.StatusLastModifiedBy.Contains(keyword)
                    || x.Address.Contains(keyword)
                    || x.Email.Contains(keyword))
                    || x.LastModifiedBy.Contains(keyword))
                    || x.CostEstimateNumber.Contains(keyword))
                    || x.ClientId.ToString().Contains(keyword))
                    || x.JobDescription.Contains(keyword))
                    || x.CreatedByDisplay.Contains(keyword))
                    || x.ModifiedByDisplay.Contains(keyword))
                    || x.InternalInstructions.Contains(keyword))
                    );
  1. 以可搜索的格式存储值。
    如果您真的需要搜索数字字段,我们可以做得比上面的更好,就像在本例中 ClientId 那么它有助于将数值存储在基于 string 的列中,因为我们的搜索参数是一个字符串。
    • 在数据库中实现字段变体的最简单方法是使用计算列,但是它必须是write computedperisted 列来实现搜索索引的好处。从表达式中创建计算列:

      CAST(ClientId as char(10))
      

同样的规则适用于可能需要对其应用函数的任何其他列,如果将函数评估移动到记录为 INSERT 或 [=20= 的时间,您将看到更好的性能],通过 SELECT.

读取的频率要低得多
  1. 规范化结构,如果查询连接到 user [=142,则大部分比较都针对 user - displayname =],那么您只有一次搜索匹配的任何用户,而不是为每个用户单独搜索一次

对于正在应用数据修改的 User,这里有一个明确的相关 table。这会在搜索查询中添加大量冗余信息。理想情况下,我们不会跨用户字段进行搜索,因为那里的任何匹配项都会调出与它们关联的所有记录,它通常不是一个好的搜索候选者,除非用户不编辑很多记录。因此,如果可以的话,将他们从一般搜索中排除,并允许用户从用户列表中进行选择以限定结果范围,或者在与主搜索并行的情况下从用户中进行搜索

现在搜索速度快得多:(假设有一个名为 ClientIdString 的新列)

jobs = jobs.Where(x => x.JMSNumber.Contains(keyword)
                    || x.ClientName.Contains(keyword)
                    || x.MatterReference.Contains(keyword)
                    || x.ContactName.Contains(keyword)
                    || x.JobShortDescription.Contains(keyword)
                    || x.Address.Contains(keyword)
                    || x.Email.Contains(keyword))
                    || x.CostEstimateNumber.Contains(keyword))
                    || x.ClientIdString.Contains(keyword))
                    || x.JobDescription.Contains(keyword))
                    || x.InternalInstructions.Contains(keyword))
                    );

假设的用户搜索,用于搜索然后按用户过滤:

var userIds = db.Users.Where(u => u.UserName.Contains(keyword))
                      .Select(u => u.Id)
                      .ToList();

//Filter to only rows that match the user lookup
if (jobs.Any())
{    
    jobs = jobs.Where(x => userIds.Contains(x.StatusLastModifiedByUserId)
                        || userIds.Contains(x.LastModifiedByUserId)
                        || userIds.Contains(x.CreatedByUserId)
                        || userIds.Contains(x.ModifiedByUserId)
                        );
}

如果模式尚未规范化,那么我强烈建议您至少将用户规范化为他们自己的 table,索引可能的用户比在 200K+ 记录中搜索更有效。

也可以直接在SQL中写查询。有时,我们可以通过 SQL 编写比通过 LINQ 实现的更高效的代码,不要为此感到难过,只要认识到它是您可以使用的众多工具之一即可。

  • 大多数 Linq 提供程序都会为您提供一个明确的机制来执行原始 SQL,这将 return 转换为 linq 表达式。这方面的细节超出了范围,但搜索是接受这种情况的特定场景。

还有其他外部选项,如 SQL 服务器全文搜索MySQL FULLTEXT 索引 甚至 Microsoft Azure SearchElastic Search。这些外部机制可用于直接 return 搜索内容,或者它们可能 return 引用,您可以使用这些引用来访问本地数据库中的记录。

  • 很多NoSQL供应商可以用来构建高效的搜索索引,我上面列出的产品只是为搜索而设计的,很可能会实现很多行业

索引

以上所有内容均假定您已在基础数据存储上实现了足够的索引。搜索可以对用户体验产生如此大的影响,值得为之付出努力。

  • 评估和实施索引超出了这个问题的范围