[英]LINQ to Entities search multiple columns, order by total weight of the matches columns
我正在實現一個搜索算法,我需要一種方法來搜索多列的數據庫。 然后,該算法將返回“最佳匹配”。 例如,假設我有一個實體:
public class Person{
public string Name { get; set; }
public int Age { get; set; }
public string Title { get; set; }
}
我的搜索方法需要接受對姓名,年齡和標題的搜索,並且在可以進行任何組合的情況下,所有搜索都是可選的 。 所有字段都有權重,我將在代碼中進行微調以獲得更好的結果。 結果應按score
排序, score
為:
matchedColumn1Weight + matchedColumn2Weight + ... + matchedColumnNWeight
讓我們假設我有一個名為people的表:
Name Age Title
-------------------------
Alice 20 Manager
Bob 21 Friend
James 20 Friend
Will 22 Manager
假設Name
的權重為1
, Age
的權重為1
, Title
的權重為1.1
。 如果我使用name = null, age = 20, title = Friend
字段進行搜索,則應首先返回James,然后是Bob,然后是Alice,然后是Will。
如何在LINQ-to-Entities中實現此類功能? 換句話說,我需要一個LINQ,在其中查詢多個可選字段,將數據庫中的每個項目映射到匹配的總列分數(其中列具有固定的預設權重),然后按該分數排序。 怎么做?
讓我們從查詢開始:
const decimal nameWeight = 1, ageWeight = 1, titleWeight = 1.1m;
string name = null;
int? age = 20;
string title = (string)"Friend";
var query = from p in db.Persons
let nameMatch = name == null || p.Name == name
let ageMatch = age == null || p.Age == age.Value
let titleMatch = title == null || p.Title == title
let score = (nameMatch ? nameWeight : 0) + (ageMatch ? ageWeight : 0) + (titleMatch ? titleWeight : 0)
where nameMatch || ageMatch || titleMatch
orderby score descending
select p;
這將起作用,但是由於SQL查詢中嵌入了最佳參數,因此它不是最佳查詢。 例如,使用上述示例參數,SQL查詢如下所示:
SELECT
[Project1].[Id] AS [Id],
[Project1].[Name] AS [Name],
[Project1].[Age] AS [Age],
[Project1].[Title] AS [Title]
FROM ( SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Age] AS [Age],
[Extent1].[Title] AS [Title],
(CASE WHEN ((CASE WHEN (@p__linq__0 IS NULL OR [Extent1].[Name] = @p__linq__1) THEN cast(1 as bit) WHEN ( NOT (@p__linq__0 IS NULL OR [Extent1].[Name] = @p__linq__1)) THEN cast(0 as bit) END) = 1) THEN cast(1 as decimal(18)) ELSE cast(0 as decimal(18)) END) + (CASE WHEN ((CASE WHEN (@p__linq__2 IS NULL OR [Extent1].[Age] = @p__linq__3) THEN cast(1 as bit) WHEN ( NOT (@p__linq__2 IS NULL OR [Extent1].[Age] = @p__linq__3)) THEN cast(0 as bit) END) = 1) THEN cast(1 as decimal(18)) ELSE cast(0 as decimal(18)) END) + (CASE WHEN ((CASE WHEN (@p__linq__4 IS NULL OR [Extent1].[Title] = @p__linq__5) THEN cast(1 as bit) WHEN ( NOT (@p__linq__4 IS NULL OR [Extent1].[Title] = @p__linq__5)) THEN cast(0 as bit) END) = 1) THEN 1.1 ELSE cast(0 as decimal(18)) END) AS [C1]
FROM [dbo].[People] AS [Extent1]
WHERE ((CASE WHEN (@p__linq__0 IS NULL OR [Extent1].[Name] = @p__linq__1) THEN cast(1 as bit) WHEN ( NOT (@p__linq__0 IS NULL OR [Extent1].[Name] = @p__linq__1)) THEN cast(0 as bit) END) = 1) OR ((CASE WHEN (@p__linq__2 IS NULL OR [Extent1].[Age] = @p__linq__3) THEN cast(1 as bit) WHEN ( NOT (@p__linq__2 IS NULL OR [Extent1].[Age] = @p__linq__3)) THEN cast(0 as bit) END) = 1) OR ((CASE WHEN (@p__linq__4 IS NULL OR [Extent1].[Title] = @p__linq__5) THEN cast(1 as bit) WHEN ( NOT (@p__linq__4 IS NULL OR [Extent1].[Title] = @p__linq__5)) THEN cast(0 as bit) END) = 1)
) AS [Project1]
ORDER BY [Project1].[C1] DESC
動態查詢部分可以通過使用我最近編寫並發布在這里的ReduceConstPredicates
幫助器方法簡單地優化,該方法在檢查了非基本對象/非結構對象的null以及此處如何編寫動態 后,在其中拋出了“ Nullable對象必須具有值”異常。 連接范圍變量的where子句 。 您只需要在最后說一遍:
query = query.ReduceConstPredicates();
並且生成的SQL變為:
SELECT
[Project1].[Id] AS [Id],
[Project1].[Name] AS [Name],
[Project1].[Age] AS [Age],
[Project1].[Title] AS [Title]
FROM ( SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Age] AS [Age],
[Extent1].[Title] AS [Title],
cast(1 as decimal(18)) + (CASE WHEN ((CASE WHEN ([Extent1].[Age] = @p__linq__0) THEN cast(1 as bit) WHEN ([Extent1].[Age] <> @p__linq__0) THEN cast(0 as bit) END) = 1) THEN cast(1 as decimal(18)) ELSE cast(0 as decimal(18)) END) + (CASE WHEN ((CASE WHEN ([Extent1].[Title] = @p__linq__1) THEN cast(1 as bit) WHEN ([Extent1].[Title] <> @p__linq__1) THEN cast(0 as bit) END) = 1) THEN 1.1 ELSE cast(0 as decimal(18)) END) AS [C1]
FROM [dbo].[People] AS [Extent1]
) AS [Project1]
ORDER BY [Project1].[C1] DESC
PS這是所使用方法的源代碼:
public static class QueryableExtensions
{
public static IQueryable<T> ReduceConstPredicates<T>(this IQueryable<T> source)
{
var reducer = new ConstPredicateReducer();
var expression = reducer.Visit(source.Expression);
if (expression == source.Expression) return source;
return source.Provider.CreateQuery<T>(expression);
}
class ConstPredicateReducer : ExpressionVisitor
{
private int evaluateConst;
private bool EvaluateConst { get { return evaluateConst > 0; } }
private ConstantExpression TryEvaluateConst(Expression node)
{
evaluateConst++;
try { return Visit(node) as ConstantExpression; }
catch { return null; }
finally { evaluateConst--; }
}
protected override Expression VisitUnary(UnaryExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var operandConst = TryEvaluateConst(node.Operand);
if (operandConst != null)
{
var result = Expression.Lambda(node.Update(operandConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
return EvaluateConst ? node : base.VisitUnary(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var leftConst = TryEvaluateConst(node.Left);
if (leftConst != null)
{
if (node.NodeType == ExpressionType.AndAlso)
return (bool)leftConst.Value ? Visit(node.Right) : Expression.Constant(false);
if (node.NodeType == ExpressionType.OrElse)
return !(bool)leftConst.Value ? Visit(node.Right) : Expression.Constant(true);
var rightConst = TryEvaluateConst(node.Right);
if (rightConst != null)
{
var result = Expression.Lambda(node.Update(leftConst, node.Conversion, rightConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
}
return EvaluateConst ? node : base.VisitBinary(node);
}
protected override Expression VisitConditional(ConditionalExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var testConst = TryEvaluateConst(node.Test);
if (testConst != null)
return Visit((bool)testConst.Value ? node.IfTrue : node.IfFalse);
}
return EvaluateConst ? node : base.VisitConditional(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var expressionConst = node.Expression != null ? TryEvaluateConst(node.Expression) : null;
if (expressionConst != null || node.Expression == null)
{
var result = Expression.Lambda(node.Update(expressionConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
return EvaluateConst ? node : base.VisitMember(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var objectConst = node.Object != null ? TryEvaluateConst(node.Object) : null;
if (objectConst != null || node.Object == null)
{
var argumentsConst = new ConstantExpression[node.Arguments.Count];
int count = 0;
while (count < argumentsConst.Length && (argumentsConst[count] = TryEvaluateConst(node.Arguments[count])) != null)
count++;
if (count == argumentsConst.Length)
{
var result = Expression.Lambda(node.Update(objectConst, argumentsConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
}
return EvaluateConst ? node : base.VisitMethodCall(node);
}
}
}
您可以使您的類IComparable像下面的代碼一樣,由Sort方法使用。 您可以使用類似的代碼創建更復雜的排序算法。 CompareTo返回-1(小於),0等於和+1(更多)。
public class Person : IComparable
{
public string Name { get; set; }
public int Age { get; set; }
public string Title { get; set; }
public List<string> order { get; set; }
public int CompareTo(object _other)
{
Person other = (Person)_other;
int results = 0;
if (this.Name != other.Name)
{
results = this.Name.CompareTo(other.Name);
}
else
{
if (this.Age != other.Age)
{
results = this.Age.CompareTo(other.Age);
}
else
{
results = this.Title.CompareTo(other.Title);
}
}
return results;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.