使用Linq表达式进行C#动态数据库筛选

4ktjp1zp  于 2022-12-06  发布在  C#
关注(0)|答案(3)|浏览(233)

我尝试创建用于过滤数据库中不同实体通用方法,以避免为每个实体创建庞大的方法,并使用非常相似的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对象,但它们仅对实体类中的属性进行操作(不使用导航属性等)。但是否可以将过滤器表达式与命名属性表达式组合?
我希望我能清楚地描述我试图达到的目标,以及与标准示例的区别,这是我的问题的根源
我将非常感激,如果有人帮助我如何创建这样的过滤器,以便比较表达式的结果在给定的参数与值,然后应用 predicate 到查询,以便运行它对sql数据库:)

krcsximq

krcsximq1#

它是这样的:

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

xxls0lw8

xxls0lw82#

Xanatos已经完全回答了你的问题,但是我只是想演示一些其他的方法来概括你的数据库表和查询语句。第一种是使用函数+接口来代替lambda:

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" 
    }
}
3df52oht

3df52oht3#

这听起来和我解决的一个问题非常相似。在我的例子中,我必须根据用Sql语法定义的复杂过滤器将对象过滤到不同的类别中。
我已经创建了一个Nuget包DynamicFilter.Sql来从Sql动态生成lambda表达式。
以下是一个示例

var filter = FilterExpression.Compile<User>("(Age > 10) and (Name like 'Alice%' or Name like 'Bob%')");

//Filter a collection
var filteredUsers = users.Where(filter);

该软件包是开源的,可在github上获得。

相关问题