在下面的 C# 代码中针对 Blog 实体定义了 Expression<Func<Blog, bool>> (PublicSpec 方法部分)
public static class BlogsSpecs
{
extension(IQueryable<Blog> blogs)
{
public IQueryable<Blog> Public()
=> blogs.Where(PublicSpec());
}
public static Expression<Func<Blog, bool>> PublicSpec()
=> b => b.Flag.HasFlag(ConfigurationFlag.IsPrivated) == false;
}
请问在 BlogPost 的导航属性 Blog 中如何使用这个表达式?
public static class BlogPostSpecs
{
extension(IQueryable<BlogPost> posts)
{
public IQueryable<BlogPost> BlogIsPublic()
=> posts.Where(p => p.BlogSite); // 如何调用 BlogsSpecs.PublicSpec()
}
}
通过 deepseek 的回答解决了,需要将 p => p.BlogSite 与 PublicSpec() 组合成一个新的表达式才行
public IQueryable<BlogPost> BlogIsPublic()
{
Expression<Func<BlogPost, Blog>> blogSelector = p => p.Blog;
var postFilter = ExpressionCombiner.Compose(BlogsSpecs.PublicSpec(), blogSelector);
return posts.Where(postFilter);
}
ExpressionCombiner 的实现如下
public static class ExpressionCombiner
{
public static Expression<Func<T, bool>> Compose<T, TProp>(
Expression<Func<TProp, bool>> predicate,
Expression<Func<T, TProp>> selector)
{
var param = selector.Parameters[0];
var body = new ParameterReplacer(selector.Body).Visit(predicate.Body);
return Expression.Lambda<Func<T, bool>>(body, param);
}
private class ParameterReplacer(Expression replacement) : ExpressionVisitor
{
protected override Expression VisitParameter(ParameterExpression node)
{
return replacement;
}
}
}
后续改进中实现了一个 For 扩展方法
public static class ExpresstionExtensions
{
extension<TProp>(Expression<Func<TProp, bool>> predicate)
{
public Expression<Func<T, bool>> For<T>(Expression<Func<T, TProp>> selector)
{
return Compose(predicate, selector);
}
}
public static Expression<Func<T, bool>> Compose<T, TProp>(
Expression<Func<TProp, bool>> predicate,
Expression<Func<T, TProp>> selector)
{
var param = selector.Parameters[0];
var body = new ParameterReplacer(selector.Body).Visit(predicate.Body);
return Expression.Lambda<Func<T, bool>>(body, param);
}
private class ParameterReplacer(Expression replacement) : ExpressionVisitor
{
protected override Expression VisitParameter(ParameterExpression node)
{
return replacement;
}
}
}
调用扩展方法的代码
public IQueryable<BlogPost> BlogIsPublic()
{
return posts.Where(BlogsSpecs.PublicSpec().For<Bloge, BlogPost>(p => p.Blog));
}
public static class BlogPostSpecs
{
public static IQueryable<BlogPost> BlogIsPublic(this IQueryable<BlogPost> posts)
{
return posts.Where(p => BlogsSpecs.PublicSpec().Invoke(p.Blog));
}
}
PublicSpec 没有 Invoke 方法,需要先调用 Compile 方法
public IQueryable<BlogPost> BlogIsPublic()
=> posts.Where(p => BlogsSpecs.PublicSpec().Compile().Invoke(p.Blog));
但这样不可行,EF Core 翻译不了,会报错
'.Where(b => Func<Blog, bool>.Invoke(b.Inner))' could not be translated.
不好意思,之前弄错了,不加 Complile,可以直接 Invoke ,但 EF Core 同样翻译不了,报错信息如下
The LINQ expression 'b => b.Flag.HasFlag((Enum)IsPrivated) == False' could not be translated
Expression Trees in C#: Practical Guide
– dudu 1天前