fix: avoid SqlSugar invalid SQL from null/HasValue KB filters
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
95940331ce
commit
7e442180cd
@ -209,33 +209,68 @@ public class KbArticleService : ApplicationService
|
|||||||
|
|
||||||
private static Expression<Func<KbArticle, bool>> BuildAdminPredicate(KbArticleListInput input)
|
private static Expression<Func<KbArticle, bool>> BuildAdminPredicate(KbArticleListInput input)
|
||||||
{
|
{
|
||||||
var keyword = input.Keyword?.Trim();
|
var keyword = NormalizeOptional(input.Keyword);
|
||||||
var status = NormalizeOptional(input.Status);
|
var status = NormalizeOptional(input.Status);
|
||||||
var audience = NormalizeOptional(input.Audience);
|
var audience = NormalizeOptional(input.Audience);
|
||||||
return x =>
|
var predicate = ExpressionPredicate.True<KbArticle>();
|
||||||
(!input.CategoryId.HasValue || x.CategoryId == input.CategoryId.Value)
|
|
||||||
&& (status == null || x.Status == status)
|
if (input.CategoryId is int categoryId)
|
||||||
&& (audience == null || x.Audience == audience)
|
{
|
||||||
&& (!input.IsPinned.HasValue || x.IsPinned == input.IsPinned.Value)
|
predicate = ExpressionPredicate.AndAlso(predicate, x => x.CategoryId == categoryId);
|
||||||
&& (keyword == null
|
}
|
||||||
|| x.Title.Contains(keyword)
|
|
||||||
|| (x.Summary != null && x.Summary.Contains(keyword))
|
if (status != null)
|
||||||
|| (x.Tags != null && x.Tags.Contains(keyword)));
|
{
|
||||||
|
predicate = ExpressionPredicate.AndAlso(predicate, x => x.Status == status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audience != null)
|
||||||
|
{
|
||||||
|
predicate = ExpressionPredicate.AndAlso(predicate, x => x.Audience == audience);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.IsPinned is bool isPinned)
|
||||||
|
{
|
||||||
|
predicate = ExpressionPredicate.AndAlso(predicate, x => x.IsPinned == isPinned);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyword != null)
|
||||||
|
{
|
||||||
|
predicate = ExpressionPredicate.AndAlso(
|
||||||
|
predicate,
|
||||||
|
x => x.Title.Contains(keyword)
|
||||||
|
|| (x.Summary != null && x.Summary.Contains(keyword))
|
||||||
|
|| (x.Tags != null && x.Tags.Contains(keyword)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return predicate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Expression<Func<KbArticle, bool>> BuildPortalPredicate(string? keyword, int? categoryId)
|
private static Expression<Func<KbArticle, bool>> BuildPortalPredicate(string? keyword, int? categoryId)
|
||||||
{
|
{
|
||||||
var kw = NormalizeOptional(keyword);
|
var kw = NormalizeOptional(keyword);
|
||||||
var now = DateTime.Now;
|
var now = DateTime.Now;
|
||||||
return x =>
|
var predicate = ExpressionPredicate.AndAlso(
|
||||||
x.Status == KbArticleStatusCodes.Published
|
ExpressionPredicate.True<KbArticle>(),
|
||||||
&& x.Audience == KbAudienceCodes.Customer
|
x => x.Status == KbArticleStatusCodes.Published
|
||||||
&& (x.ExpireTime == null || x.ExpireTime > now)
|
&& x.Audience == KbAudienceCodes.Customer
|
||||||
&& (!categoryId.HasValue || x.CategoryId == categoryId.Value)
|
&& (x.ExpireTime == null || x.ExpireTime > now));
|
||||||
&& (kw == null
|
|
||||||
|| x.Title.Contains(kw)
|
if (categoryId is int cid)
|
||||||
|| (x.Summary != null && x.Summary.Contains(kw))
|
{
|
||||||
|| (x.Tags != null && x.Tags.Contains(kw)));
|
predicate = ExpressionPredicate.AndAlso(predicate, x => x.CategoryId == cid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kw != null)
|
||||||
|
{
|
||||||
|
predicate = ExpressionPredicate.AndAlso(
|
||||||
|
predicate,
|
||||||
|
x => x.Title.Contains(kw)
|
||||||
|
|| (x.Summary != null && x.Summary.Contains(kw))
|
||||||
|
|| (x.Tags != null && x.Tags.Contains(kw)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return predicate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EnsurePortalVisible(KbArticle entity)
|
private static void EnsurePortalVisible(KbArticle entity)
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
|
using System.Linq.Expressions;
|
||||||
using Fake.Application;
|
using Fake.Application;
|
||||||
using Fake.Domain.Exceptions;
|
using Fake.Domain.Exceptions;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Oms.Application.Dtos.Kb;
|
using Oms.Application.Dtos.Kb;
|
||||||
using Oms.Application.Dtos.Shared;
|
using Oms.Application.Dtos.Shared;
|
||||||
|
using Oms.Application.Shared;
|
||||||
using Oms.Domain.KbAggregate.Entities;
|
using Oms.Domain.KbAggregate.Entities;
|
||||||
using Oms.Domain.KbAggregate.Repositories;
|
using Oms.Domain.KbAggregate.Repositories;
|
||||||
|
|
||||||
@ -23,8 +25,13 @@ public class KbCategoryService : ApplicationService
|
|||||||
[HttpGet("/kb/admin/category/list")]
|
[HttpGet("/kb/admin/category/list")]
|
||||||
public async Task<OmsItemsResult<KbCategoryOutput>> AdminListAsync([FromQuery] bool? enabledOnly = null)
|
public async Task<OmsItemsResult<KbCategoryOutput>> AdminListAsync([FromQuery] bool? enabledOnly = null)
|
||||||
{
|
{
|
||||||
var items = await CategoryRepository.GetListAsync(x =>
|
Expression<Func<KbCategory, bool>> predicate = ExpressionPredicate.True<KbCategory>();
|
||||||
!enabledOnly.HasValue || x.Enabled == enabledOnly.Value);
|
if (enabledOnly is bool enabled)
|
||||||
|
{
|
||||||
|
predicate = ExpressionPredicate.AndAlso(predicate, x => x.Enabled == enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = await CategoryRepository.GetListAsync(predicate);
|
||||||
var ordered = items.OrderBy(x => x.SortOrder).ThenBy(x => x.Id).ToList();
|
var ordered = items.OrderBy(x => x.SortOrder).ThenBy(x => x.Id).ToList();
|
||||||
return new OmsItemsResult<KbCategoryOutput>
|
return new OmsItemsResult<KbCategoryOutput>
|
||||||
{
|
{
|
||||||
|
|||||||
38
src/Oms.Application/Shared/ExpressionPredicate.cs
Normal file
38
src/Oms.Application/Shared/ExpressionPredicate.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
namespace Oms.Application.Shared;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 组合 LINQ 谓词,避免把 `HasValue` / `== null` 写进表达式导致 SqlSugar 生成非法 SQL。
|
||||||
|
/// </summary>
|
||||||
|
public static class ExpressionPredicate
|
||||||
|
{
|
||||||
|
public static Expression<Func<T, bool>> True<T>() => static _ => true;
|
||||||
|
|
||||||
|
public static Expression<Func<T, bool>> AndAlso<T>(
|
||||||
|
Expression<Func<T, bool>> left,
|
||||||
|
Expression<Func<T, bool>> right)
|
||||||
|
{
|
||||||
|
var parameter = Expression.Parameter(typeof(T), "x");
|
||||||
|
var leftBody = new ReplaceParameterVisitor(left.Parameters[0], parameter).Visit(left.Body)
|
||||||
|
?? throw new InvalidOperationException("left predicate body is null");
|
||||||
|
var rightBody = new ReplaceParameterVisitor(right.Parameters[0], parameter).Visit(right.Body)
|
||||||
|
?? throw new InvalidOperationException("right predicate body is null");
|
||||||
|
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(leftBody, rightBody), parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ReplaceParameterVisitor : ExpressionVisitor
|
||||||
|
{
|
||||||
|
private readonly ParameterExpression _from;
|
||||||
|
private readonly ParameterExpression _to;
|
||||||
|
|
||||||
|
public ReplaceParameterVisitor(ParameterExpression from, ParameterExpression to)
|
||||||
|
{
|
||||||
|
_from = from;
|
||||||
|
_to = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Expression VisitParameter(ParameterExpression node) =>
|
||||||
|
ReferenceEquals(node, _from) ? _to : base.VisitParameter(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user