将泛型func作为Linq表达式C#传递



我有一个在多个地方重复的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 => x.DeletedOn == null,但当我尝试使用上面的代码时,我得到了以下异常:

InvalidOperationException:筛选器表达式'x=>(x.DeletedOn==为实体类型"Epic"指定的"(null("无效。表达式必须接受类型为的单个参数"ProductFocus.Domain.Model.Epic",返回bool,并且不能包含导航属性的引用。

如何实现?

HasQueryFilter是一个泛型方法,其中泛型参数T与上一次对Entity<EntityType>的调用相匹配。当使用适当的类型时,手动传递表达式没有问题。但是,您尝试传递的表达式属性的类型是Expression<Func<ISoftDelete, bool>>,并且即使EntityType实现了ISoftDelete(Expression<>不是协变的(,也没有来自Expression<Func<EntityType, bool>>的隐式转换,这就是它不起作用的原因。

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

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>())

您可以更进一步,在约束为泛型类型的ModelBuilderEntityTypeBuilder<T>上编写扩展方法,并完全省略辅助类

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<T>,然后您可以使用它来链接进一步的实体配置。

最新更新