Передача generi c fun c в виде выражения Linq C# - PullRequest
1 голос
/ 03 мая 2020

У меня есть выражение 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) ;
}

Я хочу заменить все вхождения x => x.DeletedOn == null на ExcludeSoftDeleted, но я получаю следующее исключение, когда пытаюсь сделать это, используя приведенный выше код:

InvalidOperationException: выражение фильтра 'x => (x.DeletedOn == null)', указанное для типа сущности 'Epi c', недопустимо. Выражение должно принимать один параметр типа 'ProductFocus.Domain.Model.Epi c', возвращать bool и может не содержать ссылок на свойства навигации.

Как этого достичь?

1 Ответ

1 голос
/ 03 мая 2020

HasQueryFilter - это обобщенный c метод, в котором обобщенный c параметр T соответствует параметру предыдущего вызова Entity<EntityType>. У вас нет проблем с передачей выражения вручную , когда используются соответствующие типы. Однако свойство выражения, которое вы пытались передать, имеет тип Expression<Func<ISoftDelete, bool>>, и не существует неявного преобразования из Expression<Func<EntityType, bool>>, даже если EntityType реализует ISoftDelete (Expression<> не является ковариантным), поэтому оно не работает ,

Вы можете обойти это, предоставив некоторые вспомогательные классы, которые могут вернуть вам соответствующее выражение для вашей сущности.

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);
} 

Ключевым моментом здесь является то, что мы ограничили параметр generi c значением 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>())

Вы можете go сделать шаг вперед и написать методы расширения для ModelBuilder или EntityTypeBuilder<T>, ограниченные обобщенным значением c введите и опустите вспомогательный класс в целом

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 
        );
    } 
} 

Это снова работает благодаря ограничению c generic ISoftDelete. Затем вы можете вызвать их как:

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

Методы возвращают EntityTypeBuilder<T>, который затем можно использовать для объединения дальнейших конфигураций сущностей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...