C # Динамическая фильтрация базы данных с выражением Linq - PullRequest
0 голосов
/ 12 июня 2018

Я пытаюсь создать универсальный метод для фильтрации различных объектов в базе данных, чтобы избежать создания огромных методов для каждого объекта с объединением правил фильтрации с использованием очень похожих операторов if.

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

Моя концепция решения проблемы и того, что я создал на основе других сообщений, показывает фрагмент кода ниже:

public class FuelCard 
{
    public int Id { get; set; }
    public string Number { get; set; }
    public virtual User User { get; set; }
}

public static IQueryable<TEntity> ApplyFilter<TEntity, TProperty>(
    this IQueryable<TEntity> query, 
    Expression<Func<TEntity, TProperty>> expr, TProperty value)
{
    Expression<Func<TEntity, bool>> predicate = param => true;

    var filterExpression = Expression.Equal(expr, Expression.Constant(value));
    var lambda = Expression.Lambda<Func<TEntity, bool>>(filterExpression);

    predicate = predicate.And(lambda);
    return query.Where(predicate);
}

И в конце я хотел бы использовать это так:

IQueryable<FuelCard> query = // Get cards from database as IQueryable

query = query.ApplyFilter(x => x.Id, 85);
query = query.ApplyFilter(x => x.User.LastName + " " + x.User.FirstName, "Jon Green");

Я хотел бы определить анонимные выражения, описывающие, как получить значение столбцов, а затем применить различные фильтры (здесь показан простой пример с равным методом).

Но когда я вызываю Expression.Equal, я получаю сообщение об ошибке, что для Func и Int32 нет двоичного оператора.

Во всех примерах создаются объекты Expression.Parameter с именемсобственности, но там они работают только на профиправа в классе Entity (без использования свойств навигации и т. д.). Но возможно ли объединить выражение фильтра с анонимным выражением свойства?

Надеюсь, я четко описал, чего я пытаюсь достичь и в чем отличие от стандартных примеров, являющихся источникоммои проблемы

Я был бы очень признателен, если бы кто-нибудь помог мне создать такой фильтр, чтобы сравнить результат выражения в заданном параметре со значением, а затем применить предикат к запросу, чтобы выполнить его сбаза данных sql:)

Ответы [ 2 ]

0 голосов
/ 12 июня 2018

Xanatos определенно ответил на ваш вопрос в том виде, в котором он был задан, но я просто хотел продемонстрировать несколько альтернативных способов обобщения таблиц вашей базы данных и операторов запросов.Первый - использовать функцию + интерфейсы вместо лямбда-выражения:

 context.Users.Where(ReallyComplexExpression());
 private static Expression<Func<IHasName, bool>> ReallyComplexExpression()
    {
        return x =>
            x.FirstName != null && x.LastName == "Salmon" || x.FirstName == "Fish" && x.LastName == "hermonie" ||
            x.FirstName == "Thanks" && x.LastName == "for the fish";
    }

Другой подход - универсальные синглтоны или статические статические функции, которые, как правило, работают с Linq -> SQL и часто в любом случае оказываются более читабельными

Это очень грубо и не работает, но показывает основную концепцию:

public interface IHasId
{
    int Id { get; set; }
}
public interface IHasName
{
    string FirstName { get;  }
    string LastName { get;  }

}
public class Employee : IHasName, IHasId
{
    [Column("Forename")] //this ensures it is still called "Forename" in the database
    public string FirstName { get; set; }
    [Column("Surname")]
    public string LastName { get; set; }
    [Key]
    [Column("EmployeeId")]//this ensures you are following DB best practice through the same technique as above
    public int Id { get; set; }
}
public class User : IHasName, IHasId
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    [Key]
    [Column("UserId")]
    public int Id { get; set; }

    //equally you could add
    [notmapped]
    public string FullName => $"{FirstName} {LastName}"; 
 //or add this as an extension function for all IHasName
}

public class Job: IHasName, IHasId
{
    public string Name { get; set; }

    [NotMapped] //not mapped tells entity framework you dont want these fields in the DB, so in the DB their would only be "name" but in your code, firstname & lastname are pseudonyms for Name 
    public string FirstName => Name;

    [NotMapped]
    public string LastName => Name;
    [Key]
    [Column("JobId")]
    public int Id { get; set; }


}

public class ModelContext : DbContext
{
    public DbSet<Employee> Employees { get; set; }
    public DbSet<User> Users { get; set; }
    public DbSet<Job> Jobs { get; set; }
}

public static class GenericExtensions
{

    public static IQueryable<TModel> GetById<TModel>(this IQueryable<TModel> db, int Id) where TModel : class, IHasId
    {
        return db.Where(x => x.Id == Id);
    }
    public static IQueryable<TModel> GetByAnyName<TModel>(this IQueryable<TModel> db, string name) where TModel : class, IHasName
    {
        return db.Where(x => x.FirstName == name || x.LastName == name);
    }
    public static IQueryable<TModel> GetByFirstAndLastName<TModel>(this IQueryable<TModel> db, string firstName, string lastName) where TModel : class, IHasName
    {
        return db.Where(x => x.FirstName == firstName && x.LastName == lastName);
    }
}

class Program
{
    static void Main(string[] args)
    {
        ModelContext context = new ModelContext();

        var filteredEmpl =context.Employees.GetByAnyName("John").GetById(1);

        var filteredUsers =context.Users.GetByFirstAndLastName("Terry","Richards").GetById(1);

         var filteretJobs =context.Jobs.GetByAnyName("Supervisor").GetById(1); //this wouldn't work as "Name" is the DB column, so Linq->SQL would fail when confronted with "FirstName" 
    }
}
0 голосов
/ 12 июня 2018

Это что-то вроде:

public static IQueryable<TSource> WhereEqual<TSource, TProperty>(this IQueryable<TSource> query, Expression<Func<TSource, TProperty>> propertySelector, TProperty value)
{
    var body2 = Expression.Equal(propertySelector.Body, Expression.Constant(value));
    var lambda = Expression.Lambda<Func<TSource, bool>>(body2, propertySelector.Parameters);
    return query.Where(lambda);
}

Используйте это как:

IQueryable<FuelCard> query = // Get cards from database as IQueryable

query = query.WhereEqual(x => x.Id, 85);
query = query.WhereEqual(x => x.User.LastName + " " + x.User.FirstName, "Jon Green");

Как правило, несколько .Where() неявно в &&и ) между ними.Таким образом, вам нужно только создать выражение, основанное на выражении селектора свойства плюс равные плюс переданное значение, а затем вернуть .Where(), использующее это выражение.

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