C#使用Linq Expression进行动态数据库过滤

时间:2018-06-12 09:48:08

标签: c# linq dynamic linq-to-sql

我尝试创建用于过滤数据库中不同实体的通用方法,以避免使用非常相似的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类中的属性上运行(不使用导航属性等)。 但是有可能将过滤器表达式与announymous属性表达式结合起来吗?

我希望我清楚地描述了我想要实现的目标,以及与标准示例的区别是什么,这是我的问题的根源

如果有人帮助我创建这样的过滤器以便比较参数中给定的表达式的结果和值,并且将谓词应用于查询以便针对sql数据库运行它,我将非常感激:)< / p>

2 个答案:

答案 0 :(得分:4)

类似于:

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()隐含在&&(在中)之间。因此,您只需要根据属性选择器表达式加上equals加上传递的值创建表达式,然后返回使用该表达式的.Where()

答案 1 :(得分:0)

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表示 - &gt; 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" 
    }
}