My data model has a number of tables with a common parent. I am using Entity Framework and have a navigation property from the child object back to the parent, and not the other way. The reason for this is there will be 100's of children of this common parent. The where criteria for every report will be the same, as an example:
crudEngine.Read<ChildEntity>()
.Include(parameters => parameters.ParentEntity)
.Where(parameter => parameter.ParentEntity.LastUser.Contains(searchText) ||
(isInt && SqlFunctions.DatePart("year", parameter.ParentEntity.CreationDate) == intSearchText) ||
(isInt && SqlFunctions.DatePart("month", parameter.ParentEntity.CreationDate) == intSearchText) ||
(isInt && SqlFunctions.DatePart("day", parameter.ParentEntity.CreationDate) == intSearchText) ||
(isDate && SqlFunctions.DateAdd("dy", SqlFunctions.DateDiff("dy", "1900-01-01", parameter.ParentEntity.CreationDate), "1900-01-01") == dateSearchText) ||
(isInt && SqlFunctions.DatePart("year", parameter.ParentEntity.LastModifiedDate) == intSearchText) ||
(isInt && SqlFunctions.DatePart("month", parameter.ParentEntity.LastModifiedDate) == intSearchText) ||
(isInt && SqlFunctions.DatePart("day", parameter.ParentEntity.LastModifiedDate) == intSearchText) ||
(isDate && SqlFunctions.DateAdd("dy", SqlFunctions.DateDiff("dy", "1900-01-01", parameter.ParentEntity.LastModifiedDate), "1900-01-01") == dateSearchText) ||
parameter.ParentEntity.CreationUser.Contains(searchText) ||
parameter.ParentEntity.Description.Contains(searchText)...
This same criteria will be used for every child entity that references ParentEntity. If possible, I would like to somehow separate this where section into its own function like this:
private Predicate<ParentEntity> GetParentWhere(string searchText)
{
int intSearchText;
bool isInt = int.TryParse(searchText, out intSearchText);
DateTime dateSearchText;
bool isDate = DateTime.TryParse(searchText, out dateSearchText);
SubmissionStatus submissionStatus;
bool isStatus = Enum.TryParse(searchText, true, out submissionStatus);
return parent =>
(isInt && SqlFunctions.DatePart("year", parent.CreationDate) == intSearchText) ||
(isInt && SqlFunctions.DatePart("month", parent.CreationDate) == intSearchText) ||
(isInt && SqlFunctions.DatePart("day", parent.CreationDate) == intSearchText) ||
(isDate && SqlFunctions.DateAdd("dy", SqlFunctions.DateDiff("dy", "1900-01-01", parent.CreationDate), "1900-01-01") == dateSearchText) ||
(isInt && SqlFunctions.DatePart("year", parent.LastModifiedDate) == intSearchText) ||
(isInt && SqlFunctions.DatePart("month", parent.LastModifiedDate) == intSearchText) ||
(isInt && SqlFunctions.DatePart("day", parent.LastModifiedDate) == intSearchText) ||
(isDate && SqlFunctions.DateAdd("dy", SqlFunctions.DateDiff("dy", "1900-01-01", parent.LastModifiedDate), "1900-01-01") == dateSearchText) ||
parent.CreationUser.Contains(searchText) ||
parent.Description.Contains(searchText) ||
parent.LastUser.Contains(searchText) ||
(isStatus && parent.Status == (int) submissionStatus);
}
Then call this from my code, something like this:
return crudEngine.Read<ChildEntity>().Include(parameters => parameters.ParentEntity)
.Where(GetParentWhere(searchText));
This doesn't work however because the Where expression is typed with the child type, not the parent. Any thoughts on how to accomplish this?
Thanks!
The simplest option would be for your child entities to implement an interface which exposes the ParentEntity property:
public interface IHaveParentEntity
{
ParentEntity ParentEntity { get; }
}
private Expression<Func<TChildEntity, bool>> GetParentWhere<TChildEntity>(string searchText)
where TChildEntity : IHaveParentEntity
{
int intSearchText;
bool isInt = int.TryParse(searchText, out intSearchText);
DateTime dateSearchText;
bool isDate = DateTime.TryParse(searchText, out dateSearchText);
SubmissionStatus submissionStatus;
bool isStatus = Enum.TryParse(searchText, true, out submissionStatus);
return child =>
child.ParentEntity.CreationUser.Contains(searchText) ||
...
;
}
...
return crudEngine.Read<ChildEntity>()
.Include(parameters => parameters.ParentEntity)
.Where(GetParentWhere<ChildEntity>(searchText));
If you can't modify your child entities, you'll probably need to look at building an expression tree. Due to the complexity of your query, it would probably be easier to use expression re-writing rather than building the entire expression by hand:
private Expression<Func<TChildEntity, bool>> GetParentWhere<TChildEntity>(
Expression<Func<TChildEntity, ParentEntity>> parentSelector,
string searchText)
{
int intSearchText;
bool isInt = int.TryParse(searchText, out intSearchText);
DateTime dateSearchText;
bool isDate = DateTime.TryParse(searchText, out dateSearchText);
SubmissionStatus submissionStatus;
bool isStatus = Enum.TryParse(searchText, true, out submissionStatus);
Expression<Func<ParentEntity, bool>> predicate = parent =>
parent.CreationUser.Contains(searchText) ||
...
;
Expression body = ReplacementVisitor.Replace(
predicate,
predicate.Parameters[0],
parentSelector.Body);
return Expression.Lambda<Func<TChildEntity, bool>>(body,
parentSelector.Parameters[0]);
}
private sealed class ReplacementVisitor : ExpressionVisitor
{
private IList<ParameterExpression> SourceParameters { get; set; }
private Expression ToFind { get; set; }
private Expression ToReplace { get; set; }
public static Expression Replace(
LambdaExpression source,
Expression toFind,
Expression toReplace)
{
var visitor = new ReplacementVisitor
{
SourceParameters = source.Parameters,
ToFind = toFind,
ToReplace = toReplace,
};
return visitor.Visit(source.Body);
}
private Expression ReplaceNode(Expression node)
{
return (node == ToFind) ? ToReplace : node;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (SourceParameters.Contains(node)) return ReplaceNode(node);
return SourceParameters.FirstOrDefault(p => p.Name == node.Name) ?? node;
}
}
...
return crudEngine.Read<ChildEntity>()
.Include(parameters => parameters.ParentEntity)
.Where(GetParentWhere<ChildEntity>(child => child.ParentEntity, searchText));
Related
I'm using Generic repository /UoW patter in my application c#
I was using EF6 ,then i moved to EF core .
My app worked well excpet for some reason my includes doesn't work , and i got exception
Interface :
TEntity GetFirstOrDefault(
Expression<Func<TEntity, bool>> filter = null,
params Expression<Func<TEntity, object>>[] includes);
Implementation (EF core):
public virtual TEntity GetFirstOrDefault(Expression<Func<TEntity, bool>> filter = null,
params Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = dbSet;
query = includes.Aggregate(query, (current, item) => EvaluateInclude(current, item));
return query.FirstOrDefault(filter);
}
In Entity Framework EF6 , it was :
foreach (Expression<Func<TEntity, object>> include in includes)
query = query.Include(include);
The EvaluateInclude function is :
private IQueryable<TEntity> EvaluateInclude(IQueryable<TEntity> current, Expression<Func<TEntity, object>> item)
{
if (item.Body is MethodCallExpression)
{
var arguments = ((MethodCallExpression)item.Body).Arguments;
if (arguments.Count > 1)
{
var navigationPath = string.Empty;
for (var i = 0; i < arguments.Count; i++)
{
var arg = arguments[i];
var path = arg.ToString().Substring(arg.ToString().IndexOf('.') + 1);
navigationPath += (i > 0 ? "." : string.Empty) + path;
}
return current.Include(navigationPath);
}
}
return current.Include(item);
}
When I call GetFirstOrDefault function like this way , it works :
internal Domain.Entities.Project GetProject(int projectId)
{
Expression<Func<Domain.Entities.Project, bool>> funcWhere = j => (!j.IsDisabled && j.ProjectId == projectId);
return UnitOfWork.Repository<Domain.Entities.Project>().GetFirstOrDefault(funcWhere,
p => p.StatusProject,
p => p.ProjectRoles.Select(t => t.Employee),
//p => p.ProjectTeams.Select(t => t.Team.TeamEmployees.Select(e => e.Employee)),
);
}
But when I un-comment the extra include , it fails :
internal Domain.Entities.Project GetProject(int projectId)
{
Expression<Func<Domain.Entities.Project, bool>> funcWhere = j => (!j.IsDisabled && j.ProjectId == projectId);
return UnitOfWork.Repository<Domain.Entities.Project>().GetFirstOrDefault(funcWhere,
p => p.StatusProject,
p => p.ProjectRoles.Select(t => t.Employee),
p => p.ProjectTeams.Select(t => t.Team.TeamEmployees.Select(e => e.Employee)),
);
}
System.InvalidOperationExceptionInvalid include path:
'Project.ProjectTeams.Team.TeamEmployees.Select(e => e.Employee)' -
couldn't find navigation for: 'Select(e => e'
Solved :
following this answer link
I added this code that parse my lambda expression of includes :
// This method is a slight modification of EF6 source code
private bool TryParsePath(Expression expression, out string path)
{
path = null;
var withoutConvert = RemoveConvert(expression);
var memberExpression = withoutConvert as MemberExpression;
var callExpression = withoutConvert as MethodCallExpression;
if (memberExpression != null)
{
var thisPart = memberExpression.Member.Name;
string parentPart;
if (!TryParsePath(memberExpression.Expression, out parentPart))
{
return false;
}
path = parentPart == null ? thisPart : (parentPart + "." + thisPart);
}
else if (callExpression != null)
{
if (callExpression.Method.Name == "Select"
&& callExpression.Arguments.Count == 2)
{
string parentPart;
if (!TryParsePath(callExpression.Arguments[0], out parentPart))
{
return false;
}
if (parentPart != null)
{
var subExpression = callExpression.Arguments[1] as LambdaExpression;
if (subExpression != null)
{
string thisPart;
if (!TryParsePath(subExpression.Body, out thisPart))
{
return false;
}
if (thisPart != null)
{
path = parentPart + "." + thisPart;
return true;
}
}
}
}
else if (callExpression.Method.Name == "Where")
{
throw new NotSupportedException("Filtering an Include expression is not supported");
}
else if (callExpression.Method.Name == "OrderBy" || callExpression.Method.Name == "OrderByDescending")
{
throw new NotSupportedException("Ordering an Include expression is not supported");
}
return false;
}
return true;
}
// Removes boxing
private Expression RemoveConvert(Expression expression)
{
while (expression.NodeType == ExpressionType.Convert
|| expression.NodeType == ExpressionType.ConvertChecked)
{
expression = ((UnaryExpression)expression).Operand;
}
return expression;
}
#endregion
Then change my EvaluateInclude function to :
private IQueryable<TEntity> EvaluateInclude(IQueryable<TEntity> current, Expression<Func<TEntity, object>> item)
{
if (item.Body is MethodCallExpression)
{
string path;
TryParsePath(item.Body, out path);
return current.Include(path);
}
return current.Include(item);
}
And it works
I need to ignore null parameters in the where clause so that i can fetch the appropriate data with the applied filters from the user side. Inorder to acheive this, I am currently using the if..else nested approach which grows in size as the number of parameters grow. I would like to know if there is any other effecient way of handling this scenario by avoiding the number of lines and complexity and improving readablility
public List<Members> GetMembers(int currentPosition, string memberStatus,
string package, string packageStatus, string branch)
{
var members = new List<Members>();
if (package != null)
{
//include package
if (packageStatus != null)
{
//include package, packageStatus
if (branch != null)
{
//include package,packageStatus,branch
members = db.Members.Where(x => x.PackageName == package && x.PackageStatus == packageStatus && x.Branch == branch).ToList();
}
else
{
//include package,packageStatus
members = db.Members.Where(x => x.PackageName == package && x.PackageStatus == packageStatus).ToList();
}
}
else
{
if (branch != null)
{
//include package,branch
members = db.Members.Where(x => x.PackageName == package && x.Branch == branch).ToList();
}
else
{
//include package
members = db.Members.Where(x => x.PackageName == package).ToList();
}
}
}
else
{
if (packageStatus != null)
{
//include packageStatus
if (branch != null)
{
//include packageStatus,branch
members = db.Members.Where(x => x.PackageStatus == packageStatus && x.Branch == branch).ToList();
}
else
{
//include packageStatus
members = db.Members.Where(x => x.PackageStatus == packageStatus).ToList();
}
}
else
{
if (branch != null)
{
//include packageStatus,branch
members = db.Members.Where(x => x.PackageStatus == packageStatus && x.Branch == branch).ToList();
}
else
{
//include nothing
members = db.Members.ToList();
}
}
}
return members;
}
You can add those conditions to the query. It won't make for the most readable SQL, but assuming you find readable code more important and trust SQL Server's optimizer:
members = db.Members.Where(x =>
(package == null || x.PackageName == package) &&
(packageStatus == null || x.PackageStatus == packageStatus) &&
(branch == null || x.Branch == branch)
).ToList();
Alternatively, you could conditionally append Where()s to a variable of type IQueryable<Member>. See for example entity framework: conditional filter.
I would like to suggest a readable version of the method:
public List<Members> GetMembers(int currentPosition, string memberStatus,
string package, string packageStatus, string branch)
{
var members = new List<Members>();
members = db.Members.ToList();
if (package != null)
{
members = members.Where(x => x.PackageName == package);
}
if (packageStatus != null)
{
members = members.Where(x => x.PackageStatus == packageStatus);
}
if (branch != null)
{
members = members.Where(x => x.Branch == branch);
}
return members.ToList();
}
I think this works (haven't tried compiling it but it makes sense in my head)
public List<Members> GetMembers(int currentPosition, string memberStatus,
string package, string packageStatus, string branch)
{
List<Members> ret = db.Members()
if(memberStatus != null || package != null || packageStatus != null || branch != null)
{
ret = db.Members.Where(x =>
(memberStatus == null) ? (true) : (memberStatus == x.MemberStatus)
&& (package == null) ? (true) : (package == x.PackageName)
&& (packageStatus == null) ? (true) : (packageStatus == x.PackageStatus)
&& (branch == null) ? (true) : (branch == x.Branch)
).ToList();
}
return ret;
}
Is there anyway I can optimize this code into shorter?
MakeList, TrimList and etc are List type.
and Vehicle are models.
My problem is code is very long. I have 20 property in model.
if (MakeList?.Any() == true)
{
bidVehicles = bidVehicles.Where(b => MakeList.Contains(b.Vehicle.Make));
}
if (TrimList?.Any() == true)
{
bidVehicles = bidVehicles.Where(b => TrimList.Contains(b.Vehicle.Trim));
}
if (ModelList?.Any() == true)
{
bidVehicles = bidVehicles.Where(b => ModelList.Contains(b.Vehicle.Model));
}
if (StockNoList?.Any() == true)
{
bidVehicles = bidVehicles.Where(b => StockNoList.Contains(b.Vehicle.StockNo));
}
if (BodyStyleList?.Any() == true)
{
bidVehicles = bidVehicles.Where(b => BodyStyleList.Contains(b.Vehicle.Body));
}
if (ExtColorList?.Any() == true)
{
bidVehicles = bidVehicles.Where(b => ExtColorList.Contains(b.Vehicle.Exterior));
}
return bidVehicles;
For LINQ to Objects:
public static class FilterExt
{
public static IEnumerable<TItem> ApplyFilter<TProp, TItem>(this IEnumerable<TItem> list, List<TProp> filter, Func<TItem, TProp> prop)
{
if (filter == null || filter.Count == 0)
{
return list;
}
return list.Where(x => filter.Contains(prop.Invoke(x)));
}
}
...
var filtered = bidVehicles
.ApplyFilter(MakeList, x => x.Vehicle.Make)
.ApplyFilter(TrimList, x => x.Vehicle.Trim).ToList();
If you use EF (means bidVehicles is IQueryable) you have to write expression for each property because you need full predicate Expression<Func<BidVehicle, bool>> not just Func<BidVehicle, TProp>.
I'm trying to accomplish an expression function alternative
private static Expression<Func<UserProfile, bool>> CompareFilter(FilterViewModel f)
{
...
}
on this one:
private static bool CompareFilter(UserProfile profile, FilterViewModel filter)
{
if (filter.FirstName != null)
{
if (profile.FirstName != null)
{
if (profile.FirstName.CompareTo(filter.FirstName) != 0)
{
return false;
}
}
else
{
return false;
}
}
if (filter.TownId != null)
{
if (profile.TownId != filter.TownId)
{
return false;
}
}
// true if at least one of the filter interests match
if (filter.InterestsIds != null)
{
var firstInterestFound = profile.Interests
.Where(i => filter.InterestsIds.Contains(i.Id))
.FirstOrDefault();
if (firstInterestFound == null)
{
return false;
}
}
...
return true;
}
Is there a way to fit this many nested if statements to a lambda expression or something else that will work for the expression function? The idea is the verification to go trough all the if statements and to return true only if all of them are not returning false.
Thanks in advance!
First of all, your current method is so long because you are not making good use of boolean logic. It can be simplified to this:
private static bool CompareFilter(UserProfile profile, FilterViewModel filter)
{
if (filter.FirstName != null && filter.FirstName != profile.FirstName)
{
return false;
}
if (filter.TownId != null && filter.TownId != profile.TownId)
{
return false;
}
// true if at least one of the filter interests match
if (filter.InterestsIds != null &&
!profile.Interests.Any(i => filter.InterestsIds.Contains(i.Id)))
{
return false;
}
...
return true;
}
You can turn this into a big hulking expression by inverting all the logic1:
private static bool CompareFilter(UserProfile profile, FilterViewModel filter)
{
return (filter.FirstName == null || filter.FirstName == profile.FirstName) &&
(filter.TownId == null || filter.TownId == profile.TownId) &&
(filter.InterestsIds == null ||
profile.Interests.Any(i => filter.Interests.Contains(i.Id)));
// etc. etc.
}
And once you have this, it's a piece of cake to turn it into a lambda and get your Expression<T>:
private static Expression<Func<UserProfile, bool>> CompareFilter(FilterViewModel f)
{
return profile =>
(filter.FirstName == null || filter.FirstName == profile.FirstName) &&
(filter.TownId == null || filter.TownId == profile.TownId) &&
(filter.InterestsIds == null ||
profile.Interests.Any(i => filter.Interests.Contains(i.Id)));
}
Technically, you can just || together all the conditions from the first method and put a big !( ) around it: return !( (...) || (...) || (...));, but inverting everything and joining the conditions with && is a lot nicer.
My action is :
[HttpPost]
public ViewResult SearchPost(FormCollection frm)
{
IList <post> p =db.posts.Include("user").ToList();
if (Request.Form["area"] != null)
{
if ((p!=null) && (p.Any()))
{
p =p.Where(a=>a.area==Request.Form["area"]).ToList();
}
}
if (Request.Form["floor"] != null)
{
if ((p!=null) && (p.Any()))
{
p = p.Where(a => a.floor ==
Request.Form["floor"]).ToList();
}
}
if (Request.Form["garage"] != null)
{
if ((p!=null) && (p.Any()))
{
p = p.Where(a => a.garage ==
Request.Form["garage"]).ToList();
}
}
return View(p);
}
it has shown no errors. but always return null. it should return filtered post objects or simply all posts without filtering. is there any problem ?? i can't find it .
I don't know the full signatures of the types that you are dealing with, but perhaps try this code and see if you get any better results:
[HttpPost]
public ViewResult SearchPost(FormCollection frm)
{
var area = Request.Form["area"];
var floor = Request.Form["floor"];
var garage = Request.Form["garage"];
return View(db.posts.Include("user")
.Where(a => area == null || a.area == area)
.Where(a => floor == null || a.floor == floor)
.Where(a => garage == null || a.garage == garage).ToList());
}
(This is essentially a rewrite of your code down into a single query that might help you with debugging.)