使用内部集合创建 where 谓词的动态查询
Posted
技术标签:
【中文标题】使用内部集合创建 where 谓词的动态查询【英文标题】:Dynamic query for creating where predicate with inner Collection 【发布时间】:2018-09-22 03:15:25 【问题描述】:我正在为我的 MVC
EF
应用程序创建搜索功能。我正在使用动态查询创建它。并按照这个方法https://www.codeproject.com/Articles/493917/Dynamic-Querying-with-LINQ-to-Entities-and-Express
它用于为实体的bool
和string
字段创建谓词。我的应用程序中的主要实体是Applicant
EDMX Applicant
正在关注
public partial class Applicant
public Applicant()
this.ApplicantEducations = new HashSet<ApplicantEducation>();
this.ApplicantSkills = new HashSet<ApplicantSkill>();
this.Applications = new HashSet<Application>();
this.Experiences = new HashSet<Experience>();
public int Id get; set;
public string Name get; set;
public string Description get; set;
public byte[] CV_Upload1 get; set;
public string CV_Upload2 get; set;
public string email get; set;
public string password get; set;
public Nullable<System.DateTime> DOB get; set;
virtual ICollection<ApplicantEducation> ApplicantEducations get; set;
virtual ICollection<ApplicantSkill> ApplicantSkills get; set;
virtual ICollection<Application> Applications get; set;
virtual ICollection<Experience> Experiences get; set;
我想搜索,即在类型为 Institute 的申请人教育中归档的机构名称。申请人可以有一个或多个申请人教育对象。
以下是我的申请者教育的 EDMX 课程
public partial class ApplicantEducation
public int id get; set;
public Nullable<int> ApplicantId get; set;
public Nullable<int> InstituteId get; set;
public Nullable<int> EducationLevelId get; set;
public Nullable<bool> IsComplete get; set;
public Nullable<System.DateTime> DateStart get; set;
public Nullable<System.DateTime> DateEnd get; set;
public Nullable<short> GPA get; set;
public virtual EducationLevel EducationLevel get; set;
public virtual Institute Institute get; set;
public virtual Applicant Applicant get; set;
而我的研究所实体类是这样的
public class Institute
public int Id get; set;
public string Name get; set;
因此,用户将通过指定机构名称进行搜索,所有申请人都将从该机构获得教育。
正如我上面提到的链接。下面以字符串字段谓词构建为例进行演示
private static Expression<Func<TDbType, bool>> ApplyStringCriterion<TDbType,
TSearchCriteria>(TSearchCriteria searchCriteria, PropertyInfo searchCriterionPropertyInfo,
Type dbType, MemberInfo dbFieldMemberInfo, Expression<Func<TDbType, bool>> predicate)
// Check if a search criterion was provided
var searchString = searchCriterionPropertyInfo.GetValue(searchCriteria) as string;
if (string.IsNullOrWhiteSpace(searchString))
return predicate;
// Then "and" it to the predicate.
// e.g. predicate = predicate.And(x => x.firstName.Contains(searchCriterion.FirstName)); ...
// Create an "x" as TDbType
var dbTypeParameter = Expression.Parameter(dbType, @"x");
// Get at x.firstName
var dbFieldMember = Expression.MakeMemberAccess(dbTypeParameter, dbFieldMemberInfo);
// Create the criterion as a constant
var criterionConstant = new Expression[] Expression.Constant(searchString) ;
// Create the MethodCallExpression like x.firstName.Contains(criterion)
var containsCall = Expression.Call(dbFieldMember, StringContainsMethod, criterionConstant);
// Create a lambda like x => x.firstName.Contains(criterion)
var lambda = Expression.Lambda(containsCall, dbTypeParameter) as Expression<Func<TDbType, bool>>;
// Apply!
return predicate.And(lambda);
上面的代码用于为包含在主实体类 (Applicant) 中的简单字符串字段构建谓词。但是申请人也有申请人教育集合,所以我的问题是如何为linq
的where子句(方法)创建一个动态查询(谓词),这样当用户搜索机构名称时,所有申请人都会以相同的教育被检索。
我的搜索条件如下,
public class SearchCriteriaVM
public int Id get; set;
public string Name get; set;
public DateTime? DOB get; set;
public string Description get; set;
public ICollection<Models.ApplicantEducationVM> ApplicantEducations get; set;
public ICollection<Models.ExperienceVM> Experiences get; set;
public ICollection<Models.ApplicantSkillsVM> ApplicantSkills get; set;
public ICollection<Models.ApplicationsVM> Applications get; set;
我有点迷茫,这怎么可能。
谢谢
【问题讨论】:
有可能(必须生成类似outer.Collection.Any(inner_predicate)
的内容),但不确定您将如何指定标准,因为您所遵循的示例是从一个简单的平面对象中获取标准信息。跨度>
@IvanStoev 我已经用标准更新了我的问题
你已经在这里回答了这个问题:请参考这个链接***.com/questions/25508595/…
@SwetaNair 好的,但是如果你在这里回答并解释,那么我可以接受。
【参考方案1】:
在您的情况下,我们需要的基本内容是使用 EF 的动态查询构建器。即一个基本的“匹配”方法,它包含 IQueryable 格式的数据、搜索词和过滤记录的属性。 “Match”方法是我们需要在代码中使用的方法。
public static IQueryable<T> Match<T>(
IQueryable<T> data,
string searchTerm,
IEnumerable<Expression<Func<T, string>>> filterProperties)
var predicates = filterProperties.Select(selector =>
selector.Compose(value =>
value != null && value.Contains(searchTerm)));
var filter = predicates.Aggregate(
PredicateBuilder.False<T>(),
(aggregate, next) => aggregate.Or(next));
return data.Where(filter);
要构建这个表达式方法,我们需要一个Compose方法,以便它可以接受需要搜索的参数。
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
这将组成并返回一个 lambda 表达式,但要构建此方法,我们需要“Replace”扩展方法。顾名思义,此方法会将一个表达式的所有实例替换为另一个。
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
internal class ReplaceVisitor : ExpressionVisitor
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
this.from = from;
this.to = to;
public override Expression Visit(Expression node)
return node == from ? to : base.Visit(node);
回到实际的“匹配”方法,我们需要一个谓词构建器来帮助我们呈现与搜索相关的 AND、OR 查询。
因此,谓词构建器将如下所示:
public static class PredicateBuilder
public static Expression<Func<T, bool>> True<T>() return f => true;
public static Expression<Func<T, bool>> False<T>() return f => false;
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
var secondBody = expr2.Body.Replace(
expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
var secondBody = expr2.Body.Replace(
expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
因此,我们需要一个 Match 方法,该方法将根据您的要求运行。
如果您需要任何进一步的帮助,请告诉我,具体取决于您的模型结构。
【讨论】:
基本上我使用SearchCriteria
而不是搜索词作为字符串。这个Search Criteria
是一个恰好包含属性和集合的复杂对象。您可以在问题中看到标准类。如果您尝试在代码中使用它,即控制器的操作方法来获取结果,您会得到更好的主意
搜索词是否基于任一属性?还是会在所有属性上?所有可观察的集合都将被搜索,属性将是搜索词。是这样吗?
它将搜索全部或部分或任何属性或属性。例如,用户可以只指定一个ApplicantEducation
或者可以指定所有教育。所以它不是那么简单。在您的回答中,您可能必须为Any()
实施扩展方法>查看@IvanStoev 评论【参考方案2】:
您可以使用以下方法在 Lambda 表达式中创建 Dynamic Where 子句:
public ActionResult GetRecords(int? classId, string name, bool isAll = false)
var allRecords = repository.Students;
if (!isAll)
//Retrieve active records only
allRecords = allRecords.Where(m => m.StatusId == 1);
if (!string.IsNullOrEmpty(name))
allRecords = allRecords.Where(m => m.Name.StartsWith(name));
if (classId.HasValue)
allRecords = allRecords.Where(m => m.ClassId == classId);
// other stuff
同样,可以应用以下方法,以便仅检索以 “query” 参数值开头的记录,如果 “query” 参数值则检索所有记录为空:
IQueryable<StudentViewModel> students = repository.Students.Select(m =>
new StudentViewModel
Id = m.Id,
Name = m.Name + " " + m.Surname
);
if (!string.IsNullOrEmpty(query))
students = students.Where(m => m.Name.StartsWith(query));
或“性能不佳”的另一种方式:
.Where(m => string.IsNullOrEmpty(query) || m.Name.StartsWith(query));
希望这会有所帮助...
【讨论】:
请参阅我添加到我的问题中的链接并阅读您将更好地了解我想要做什么。顺便说一句,where
扩展方法确实在调用时执行 sql 或延迟到ForEach
ing?
你能创建一个检测搜索实体的触发器值吗?您还可以使用合并多个实体的 View(以 dB 为单位)或 ViewModel,然后从多个实体中获取所有过滤值?您还可以在 EF 中使用原始查询,如 Advanced Entity Framework 6 Scenarios for an MVC 5 Web Application (12 of 12) 中所述。以上是关于使用内部集合创建 where 谓词的动态查询的主要内容,如果未能解决你的问题,请参考以下文章