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.
Related
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;
}
the following is the code and I do not know how to achieve the following using lambda
public void AddClimber(string name, int age, Skill skill)
{
if (GetClimber(name) == null)
{
Climber toAdd = new Climber(name, age, skill);
this.climbers.Add(toAdd);
if (AddedLowLevelClimer != null)
{
if (skill == Skill.LOW) { AddedLowLevelClimer(toAdd); }
}
else if (AddedMediumLevelClimer != null)
{
if (skill == Skill.LOW) { AddedMediumLevelClimer(toAdd); }
}
else if (AddedHighLevelClimer != null)
{
if (skill == Skill.LOW) { AddedHighLevelClimer(toAdd); }
}
else if (AddedProLevelClimer != null)
{
if (skill == Skill.LOW) { AddedProLevelClimer(toAdd); }
}
}
}
You may try to use a null coalescing operator to choose the first non-null handler:
var target = AddedLowLevelClimer
?? AddedMediumLevelClimer
?? AddedHighLevelClimer
?? AddedProLevelClimer;
if (target != null)
{
if (skill == Skill.LOW)
target(toAdd);
}
I am trying to write an extension method that compares objects based on their fields.
I have this:
public static class MyExtensions
{
public static bool FieldsEquals(this object o, object other)
{
if (ReferenceEquals(o, other))
return true;
if (o == null || other == null || o.GetType() != other.GetType())
return false;
foreach (var f in o.GetType().GetFields())
{
// is this a correct test ???
bool isEnumerable = f.FieldType != typeof(string) &&
typeof(IEnumerable).IsAssignableFrom(f.FieldType);
if (!isEnumerable)
{
if (!f.GetValue(o).Equals(f.GetValue(other)))
return false;
}
else
{
// convert both to IEnumerable and check if equal
}
}
return true;
}
}
I am struggling with the case of the field being a collection; I need to detect that case and then check the collections are the same (same number of elements and f.GetValue(o)[i] == f.GetValue(other)[i].
Any help?
Okay, like others already mentionend. There are a lot of edge cases..
I would recommend to use recursion for this kind of problem.
This method should also check arrays or lists that contain objects:
public static bool FieldsEquals(this object o1, object o2)
{
if (ReferenceEquals(o1, o2))
return true;
if (o1 == null || o2 == null || o1.GetType() != o2.GetType())
return false;
if (o1 is IEnumerable enumerable1 && o2 is IEnumerable enumerable2)
{
var enumerator1 = enumerable1.GetEnumerator();
var enumerator2 = enumerable2.GetEnumerator();
while(enumerator1.MoveNext())
{
if (!enumerator2.MoveNext())
{
return false;
}
if (!enumerator1.Current.FieldsEquals(enumerator2.Current))
{
return false;
}
}
}
else
{
foreach (var f in o1.GetType().GetFields())
{
var val1 = f.GetValue(o1);
var val2 = f.GetValue(o2);
if (val1 == null || val2 == null) continue;
if (val1 is IEnumerable e1 && val2 is IEnumerable e2)
{
if (!e1.FieldsEquals(e2))
{
return false;
}
}
else
{
if (!val1.Equals(val2))
{
return false;
}
}
}
}
return true;
}
To be honest, there are a lot of problems here. What if the types are basic types (like int) or Structs (datetime,etc). What if the enumerable fields contain classes not basic types? or the properties are classes?
See this question for some guidelines on deep equality: Compare the content of two objects for equality
All that said, here is my crack at the code you mentioned
public static bool FieldsEquals(this object o, object other)
{
if (ReferenceEquals(o, other)) return true;
if (o == null || other == null || o.GetType() != other.GetType()) return false;
foreach (var f in o.GetType().GetFields())
{
bool isEnumerable = f.GetValue(o).GetType().IsAssignableFrom(typeof(System.Collections.IEnumerable));// but is not a string
if (!isEnumerable)
{
if (!f.GetValue(o).Equals(f.GetValue(other))) return false;
}
else
{
var first = ((System.Collections.IEnumerable)f.GetValue(o)).Cast<object>().ToArray();
var second = ((System.Collections.IEnumerable)f.GetValue(other)).Cast<object>().ToArray();
if (first.Length != second.Length)
return false;
for (int i = 0; i < first.Length; i++)
{
if (first[i] != second[i]) //assumes they are basic types, which implement equality checking. If they are classes, you may need to recursively call this method
return false;
}
}
}
return true;
}
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));
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.)