【问题标题】:Passing generic func as Linq expression C#将泛型 func 作为 Linq 表达式 C#
【发布时间】:2020-05-03 06:15:25
【问题描述】:

我有一个在多个地方重复的 Linq 表达式。我想集中定义 Linq 表达式并在所有这些地方使用它。这是代码:

 public interface ISoftDelete
 {
    DateTime? DeletedOn { get; set; }
 }

 public class BaseModel : ISoftDelete
 {
    public int Id { get; set; }
    public DateTime? DeletedOn { get; set; }
 }

 public class Epic: BaseModel {
 }

 public class Feature: BaseModel {
 }

 public class UserStory: BaseModel {
 }

 public class ProductFocusDbContext : DbContext
 {
        public ProductFocusDbContext(DbContextOptions<ProductFocusDbContext> options) : base(options) { }

        public DbSet<Epic> Epics { get; set; }
        public DbSet<Feature> Features { get; set; }
        public DbSet<UserStory> UserStories { get; set; }

      protected override void OnModelCreating(ModelBuilder modelBuilder)
      {

            // Epic
            modelBuilder.Entity<Epic>().HasQueryFilter(ExcludeSoftDeleted);

            // Feature
            modelBuilder.Entity<Feature>().HasQueryFilter(x => x.DeletedOn == null);

            // User Story
            modelBuilder.Entity<UserStory>().HasQueryFilter(x => x.DeletedOn == null);

      }

       System.Linq.Expressions.Expression<Func<ISoftDelete, bool>> ExcludeSoftDeleted = (x => x.DeletedOn == null) ;
}

我想用 ExcludeSoftDeleted 替换所有出现的 x =&gt; x.DeletedOn == null,但是当我尝试使用上述代码执行此操作时出现以下异常:

InvalidOperationException: 过滤器表达式 'x => (x.DeletedOn == 为实体类型“Epic”指定的 null)' 无效。表达方式 必须接受一个类型的参数 'ProductFocus.Domain.Model.Epic',返回布尔值,可能不包含 对导航属性的引用。

我怎样才能实现它?

【问题讨论】:

  • minimal reproducible example 在这里很有用。
  • 好的,我会尝试创建一个分钟。代表。示例
  • 我用一个最小的可重现示例更新了我的问题
  • 无法重现。我不能复制、粘贴和编译,更别说运行了。理想情况下我想运行它,但至少编译它。请将您的代码复制到一个新的控制台应用程序并让它编译/运行。
  • 谢谢@pinkfloydx33,这是我尝试过的,它奏效了。如果您可以抽出一些时间将其作为答案,并且如果可以的话,还请解释一下为什么接口类型不起作用,而我不得不使用具体的类型来制作有效的 Linq 表达式。谢谢!

标签: c# linq


【解决方案1】:

HasQueryFilter 是一个泛型方法,其中泛型参数T 与之前对Entity&lt;EntityType&gt; 的调用相匹配。在使用适当的类型时,您可以手动 传递表达式。但是,您尝试传递的表达式属性是 Expression&lt;Func&lt;ISoftDelete, bool&gt;&gt; 类型,并且没有来自 Expression&lt;Func&lt;EntityType, bool&gt;&gt; 的隐式转换,即使 EntityType 实现 ISoftDelete(Expression&lt;&gt; 不是协变的),这就是它不起作用的原因.

您可以通过提供一些帮助类来解决此问题,这些帮助类可以为您的实体返回适当的表达式。

public static class SoftDeleteHelper<T> 
    where T: ISoftDelete // constrain generic type to interface 
{
    public static Expression<Func<T, bool>> ExcludeSoftDeleted 
        => (x => x.DeletedOn == null):
}

然后您可以在查询过滤器中引用此内容:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Epic>().HasQueryFilter(SoftDeleteHelper<Epic>.ExcludeSoftDeleted);
    modelBuilder.Entity<Feature>().HasQueryFilter(SoftDeleteHelper<Feature>.ExcludeSoftDeleted);
    modelBuilder.Entity<UserStory>().HasQueryFilter(SoftDeleteHelper<UserStory>.ExcludeSoftDeleted);
} 

这里的关键是我们将泛型参数限制为ISoftDelete,这保证了实体类型上存在DeletedOn 属性。

或者,您可以将其定义为返回实体表达式的方法。如果您有其他需要约束到不同接口的查询过滤器,这可能更合适:

public static class ExpressionHelper
{
    public static Expression<Func<T, bool>> ExcludeSoftDeleted<T>() 
        where T: ISoftDelete // constrained to interface
        => (x => x.DeletedOn == null);
}

然后可以像下面这样使用(注意这与上面不同,需要(),因为您正在调用一个返回表达式而不是引用属性的函数)

modelBuilder.Entity<Epic>().HasQueryFilter(ExpressionHelper.ExcludeSoftDeleted<Epic>())

您可以更进一步,在 ModelBuilder 或 EntityTypeBuilder&lt;T&gt; 上编写限制为泛型类型的扩展方法,并完全省略辅助类

public static class EntityBuilderExtensions
{
    // extension method on the main builder 
    public static EntityTypeBuilder<T> EntityWithSoftDelete<T>(
        this ModelBuilder builder) 
        where T: class, ISoftDelete // extra class constraint required by Entity<>
    {
        return builder.Entity<T>().WithSoftDelete();
    }
    // extension method on the result of Entity<T>
    public static EntityTypeBuilder<T> WithSoftDelete<T>(
        this EntityTypeBuilder<T> builder) 
        where T: class, ISoftDelete // extra class constraint required by Entity<>
    {
        return builder.HasQueryFilter(
            e => e.DeletedOn == null 
        );
    } 
} 

由于ISoftDelete 上的通用约束,这再次有效。然后你可以这样称呼他们:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Epic>().WithSoftDelete();
    // or
    modelBuilder.EntityWithSoftDelete<Feature>();
} 

这些方法返回一个EntityTypeBuilder&lt;T&gt;,然后您可以使用它来链接更多的实体配置。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多