replace dynamic query with linq
This commit is contained in:
parent
4073ff38eb
commit
44a3db417b
71
Oqtane.Server/Extensions/QueryableExtensions.cs
Normal file
71
Oqtane.Server/Extensions/QueryableExtensions.cs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Linq;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Oqtane.Extensions
|
||||||
|
{
|
||||||
|
public static class QueryableExtensions
|
||||||
|
{
|
||||||
|
public static IQueryable<T> FilterByItems<T, TItem>(this IQueryable<T> query, IEnumerable<TItem> items,
|
||||||
|
Expression<Func<T, TItem, bool>> filterPattern, bool isOr)
|
||||||
|
{
|
||||||
|
Expression predicate = null;
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
var itemExpr = Expression.Constant(item);
|
||||||
|
var itemCondition = ExpressionReplacer.Replace(filterPattern.Body, filterPattern.Parameters[1], itemExpr);
|
||||||
|
if (predicate == null)
|
||||||
|
predicate = itemCondition;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
predicate = Expression.MakeBinary(isOr ? ExpressionType.OrElse : ExpressionType.AndAlso, predicate,
|
||||||
|
itemCondition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate ??= Expression.Constant(false);
|
||||||
|
var filterLambda = Expression.Lambda<Func<T, bool>>(predicate, filterPattern.Parameters[0]);
|
||||||
|
|
||||||
|
return query.Where(filterLambda);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpressionReplacer : ExpressionVisitor
|
||||||
|
{
|
||||||
|
readonly IDictionary<Expression, Expression> _replaceMap;
|
||||||
|
|
||||||
|
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
|
||||||
|
{
|
||||||
|
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
[return: NotNullIfNotNull(nameof(node))]
|
||||||
|
public override Expression Visit(Expression node)
|
||||||
|
{
|
||||||
|
if (node != null && _replaceMap.TryGetValue(node, out var replacement))
|
||||||
|
return replacement;
|
||||||
|
return base.Visit(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
|
||||||
|
{
|
||||||
|
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
|
||||||
|
{
|
||||||
|
return new ExpressionReplacer(replaceMap).Visit(expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
|
||||||
|
{
|
||||||
|
if (lambda.Parameters.Count != toReplace.Length)
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
|
||||||
|
return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
|
||||||
|
.ToDictionary(i => (Expression)lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,9 @@ using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||||
|
using Oqtane.Extensions;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Models;
|
using Oqtane.Models;
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
@ -24,79 +27,80 @@ namespace Oqtane.Repository
|
||||||
|
|
||||||
var keywords = SearchUtils.GetKeywords(searchQuery.Keywords);
|
var keywords = SearchUtils.GetKeywords(searchQuery.Keywords);
|
||||||
|
|
||||||
// using dynamic SQL for query performance (this could be replaced with linq if the exact query structure can be replicated)
|
var searchContents = db.SearchContentWord
|
||||||
var parameters = new List<object>();
|
.AsNoTracking()
|
||||||
parameters.Add(searchQuery.SiteId);
|
.Include(item => item.SearchContent)
|
||||||
|
.Include(item => item.SearchWord)
|
||||||
var query = "SELECT sc.*, Count ";
|
.Where(item => item.SearchContent.SiteId == searchQuery.SiteId)
|
||||||
query += "FROM ( ";
|
.FilterByItems(keywords, (item, keyword) => item.SearchWord.Word.StartsWith(keyword), true)
|
||||||
query += "SELECT sc.SearchContentId, SUM(Count) AS Count ";
|
.GroupBy(item => new
|
||||||
query += "FROM SearchContent sc ";
|
|
||||||
query += "INNER JOIN SearchContentWord scw ON sc.SearchContentId = scw.SearchContentId ";
|
|
||||||
query += "INNER JOIN SearchWord sw ON scw.SearchWordId = sw.SearchWordId ";
|
|
||||||
query += "WHERE sc.SiteId = {0} ";
|
|
||||||
if (keywords.Count > 0)
|
|
||||||
{
|
|
||||||
query += "AND ( ";
|
|
||||||
for (int index = 0; index < keywords.Count; index++)
|
|
||||||
{
|
{
|
||||||
query += (index == 0 ? "" : "OR ") + "Word LIKE {" + parameters.Count + "} ";
|
item.SearchContent.SearchContentId,
|
||||||
parameters.Add(keywords[index] + "%");
|
item.SearchContent.SiteId,
|
||||||
}
|
item.SearchContent.EntityName,
|
||||||
query += " ) ";
|
item.SearchContent.EntityId,
|
||||||
}
|
item.SearchContent.Title,
|
||||||
query += "GROUP BY sc.SearchContentId ";
|
item.SearchContent.Description,
|
||||||
query += ") AS Scores ";
|
item.SearchContent.Body,
|
||||||
query += "INNER JOIN SearchContent sc ON sc.SearchContentId = Scores.SearchContentId ";
|
item.SearchContent.Url,
|
||||||
|
item.SearchContent.Permissions,
|
||||||
|
item.SearchContent.ContentModifiedBy,
|
||||||
|
item.SearchContent.ContentModifiedOn,
|
||||||
|
item.SearchContent.AdditionalContent,
|
||||||
|
item.SearchContent.CreatedOn
|
||||||
|
})
|
||||||
|
.Select(result => new SearchContent
|
||||||
|
{
|
||||||
|
SearchContentId = result.Key.SearchContentId,
|
||||||
|
SiteId = result.Key.SiteId,
|
||||||
|
EntityName = result.Key.EntityName,
|
||||||
|
EntityId = result.Key.EntityId,
|
||||||
|
Title = result.Key.Title,
|
||||||
|
Description = result.Key.Description,
|
||||||
|
Body = result.Key.Body,
|
||||||
|
Url = result.Key.Url,
|
||||||
|
Permissions = result.Key.Permissions,
|
||||||
|
ContentModifiedBy = result.Key.ContentModifiedBy,
|
||||||
|
ContentModifiedOn = result.Key.ContentModifiedOn,
|
||||||
|
AdditionalContent = result.Key.AdditionalContent,
|
||||||
|
CreatedOn = result.Key.CreatedOn,
|
||||||
|
Count = result.Sum(group => group.Count)
|
||||||
|
});
|
||||||
|
|
||||||
if (searchQuery.Properties != null && searchQuery.Properties.Any())
|
if (searchQuery.Properties != null && searchQuery.Properties.Any())
|
||||||
{
|
{
|
||||||
query += "LEFT JOIN SearchContentProperty scp ON sc.SearchContentId = scp.SearchContentId ";
|
searchContents = searchContents.Include(item => item.SearchContentProperties);
|
||||||
}
|
}
|
||||||
query += "WHERE sc.SiteId = {0} ";
|
|
||||||
if (!string.IsNullOrEmpty(searchQuery.IncludeEntities))
|
if (!string.IsNullOrEmpty(searchQuery.IncludeEntities))
|
||||||
{
|
{
|
||||||
query += "AND sc.EntityName IN ( ";
|
searchContents = searchContents.Where(item => searchQuery.IncludeEntities.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(item.EntityName));
|
||||||
var entities = searchQuery.IncludeEntities.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
for (int index = 0; index < entities.Length; index++)
|
|
||||||
{
|
|
||||||
query += (index == 0 ? "" : ", ") + "{" + parameters.Count + "} ";
|
|
||||||
parameters.Add(entities[index]);
|
|
||||||
}
|
|
||||||
query += " ) ";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(searchQuery.ExcludeEntities))
|
if (!string.IsNullOrEmpty(searchQuery.ExcludeEntities))
|
||||||
{
|
{
|
||||||
query += "AND sc.EntityName NOT IN ( ";
|
searchContents = searchContents.Where(item => !searchQuery.ExcludeEntities.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(item.EntityName));
|
||||||
var entities = searchQuery.ExcludeEntities.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
for (int index = 0; index < entities.Length; index++)
|
|
||||||
{
|
|
||||||
query += (index == 0 ? "" : ", ") + "{" + parameters.Count + "} ";
|
|
||||||
parameters.Add(entities[index]);
|
|
||||||
}
|
|
||||||
query += " ) ";
|
|
||||||
}
|
}
|
||||||
if (searchQuery.FromDate.ToString() != DateTime.MinValue.ToString())
|
|
||||||
|
if (searchQuery.FromDate.Date != DateTime.MinValue.Date)
|
||||||
{
|
{
|
||||||
query += "AND sc.ContentModifiedOn >= {" + parameters.Count + "} ";
|
searchContents = searchContents.Where(item => item.ContentModifiedOn >= searchQuery.FromDate);
|
||||||
parameters.Add(searchQuery.FromDate);
|
|
||||||
}
|
}
|
||||||
if (searchQuery.ToDate.ToString() != DateTime.MaxValue.ToString())
|
|
||||||
|
if (searchQuery.ToDate.Date != DateTime.MaxValue.Date)
|
||||||
{
|
{
|
||||||
query += "AND sc.ContentModifiedOn <= {" + parameters.Count + "} ";
|
searchContents = searchContents.Where(item => item.ContentModifiedOn <= searchQuery.ToDate);
|
||||||
parameters.Add(searchQuery.ToDate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchQuery.Properties != null && searchQuery.Properties.Any())
|
if (searchQuery.Properties != null && searchQuery.Properties.Any())
|
||||||
{
|
{
|
||||||
foreach (var property in searchQuery.Properties)
|
foreach (var property in searchQuery.Properties)
|
||||||
{
|
{
|
||||||
query += "AND ( scp.Key = {" + parameters.Count + "} ";
|
searchContents = searchContents.Where(item => item.SearchContentProperties.Any(p => p.Name == property.Key && p.Value == property.Value));
|
||||||
parameters.Add(property.Key);
|
|
||||||
query += "AND scp.Value = {" + parameters.Count + "} ) ";
|
|
||||||
parameters.Add(property.Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await db.SearchContent.FromSql(FormattableStringFactory.Create(query, parameters.ToArray())).ToListAsync();
|
return await searchContents.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchContent AddSearchContent(SearchContent searchContent)
|
public SearchContent AddSearchContent(SearchContent searchContent)
|
||||||
|
|
|
@ -20,5 +20,7 @@ namespace Oqtane.Models
|
||||||
public DateTime ModifiedOn { get; set; }
|
public DateTime ModifiedOn { get; set; }
|
||||||
|
|
||||||
public SearchWord SearchWord { get; set; }
|
public SearchWord SearchWord { get; set; }
|
||||||
|
|
||||||
|
public SearchContent SearchContent { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user