Create Dynamic LINQ BinaryExpression using data in related tables - linq

I have an application where all queries are created dynamically based on a simple data message received by a WCF service. A data message is, put simply, a collection of columnname/column value pairs, with the addition of an operator, e.g. Equals, Less Than, etc.
Simple Data Message of ColumnName-Value-Operator
Name, Joe, Equals
Age, 35, Less Than
Occupation, Geek, Equals
Rate, 1000, Greater Than
etc...
I have been somewhat successfully using dynamic binary expressions based on the contents of the datamessage.
BinaryExpression expression = null;
ParameterExpression parameter = Expression.Parameter(typeof(MessageType), "p");
foreach (row in DataMessage)
{
BinaryExpression exp = DataLib.MakeQueryFilter(typeof(MessageType),
row.ColumnName,row.ColumnValue,column.DataOperator.ToString(), parameter);
expression = expression == null ? exp : Expression.AndAlso(expression, exp);
results = DataContext.MessageType.Where(Expression.Lambda<Func<Media, bool>>(expression, parameter));
}
public static BinaryExpression MakeQueryFilter(Type type, string propertyName, object value, string dataoperator, ParameterExpression parameter)
{
//var type = oType.GetType();
object queryvalue = null;
var property = type.GetProperty(propertyName);
Type propertyType = property.PropertyType;
if ((propertyType.IsGenericType) && (propertyType.GetGenericTypeDefinition() == typeof(Nullable)))
propertyType = propertyType.GetGenericArguments()[0];
// convert the value appropriately
if (propertyType == typeof(System.Int32))
queryvalue = Convert.ToInt32(value);
if (property.PropertyType == typeof(DateTime))
queryvalue = Convert.ToDateTime(value);
if (property.PropertyType == typeof(Double))
queryvalue = Convert.ToDouble(value);
if (property.PropertyType == typeof(String))
queryvalue = Convert.ToString(value);
if (property.PropertyType == typeof(Guid))
queryvalue = new Guid(value.ToString());
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var constantValue = Expression.Constant(queryvalue);
Type[] types = new Type[2];
types.SetValue(typeof(Expression), 0);
types.SetValue(typeof(Expression), 1);
var methodInfo = typeof(Expression).GetMethod(dataoperator, types);
var equality2 = (BinaryExpression)methodInfo.Invoke(null, new object[] { propertyAccess, constantValue });
return equality2;
}
The problem I am encountering is when I want to query through a relationship to a value in another table, and even go two->nth relationships deep. Something like this:
Name, Joe, Equals
Age, 35, Less Than
Jobs.Occupation, Geek, Equals
Jobs.Occupation.Salary.Rate, 1000, Greater Than
I have no problem writing the LINQ query by hand:
var results = from m in DataContext.MessageType
where m.Name == "Joe"
& m.Age == 35
& m.Jobs.Occupation == "Geek"
& m.Jobs.Occupation.Salaray.Rate >= 1000
select m;
Any pointers how I can dynamically create this query? Any help is greatly appreciated. Thanks.
Eric S.

Note the use of expanding PropertyAccess.
{
BinaryExpression expression = null;
ParameterExpression parameter = Expression.Parameter(typeof(DataContext.Table), "p");
string columns = "Relation1.Relation2.Column1";
string value = "ABC123";
BinaryExpression exp = MakeQueryFilter(typeof(DataContext.Table), columns,value,'Equal', parameter);
expression = expression == null ? exp : Expression.AndAlso(expression, exp);
results = DataContext.Table.Where(Expression.Lambda<Func<Table, bool>>(expression, parameter));
}
public static BinaryExpression MakeQueryFilter(Type type, string propertyNames, object value, string dataoperator, ParameterExpression parameter)
{
string[] acolumns = columns.Split('.');
var property = typeof (MediaEvent).GetProperty(acolumns[0]);
MemberExpression propertyAccess = Expression.MakeMemberAccess(parameter, property);
Type propertyType = property.PropertyType;
if ((propertyType.IsGenericType) && (propertyType.GetGenericTypeDefinition() == typeof(Nullable)))
propertyType = propertyType.GetGenericArguments()[0];
if (acolumns.Length > 1)
{
for (int i = 1; i < acolumns.Length; i++)
{
propertyAccess = Expression.MakeMemberAccess(propertyAccess, propertyAccess.Type.GetProperty(acolumns[i]));
}
}
object queryvalue = null;
// convert the value appropriately
if (propertyType == typeof(System.Int32))
queryvalue = Convert.ToInt32(value);
if (property.PropertyType == typeof(DateTime))
queryvalue = Convert.ToDateTime(value);
if (property.PropertyType == typeof(Double))
queryvalue = Convert.ToDouble(value);
if (property.PropertyType == typeof(String))
queryvalue = Convert.ToString(value);
if (property.PropertyType == typeof(Guid))
queryvalue = new Guid(value.ToString());
var constantValue = Expression.Constant(queryvalue);
Type[] types = new Type[2];
types.SetValue(typeof(Expression), 0);
types.SetValue(typeof(Expression), 1);
var methodInfo = typeof(Expression).GetMethod(dataoperator, types);
var equality2 = (BinaryExpression)methodInfo.Invoke(null, new object[] { propertyAccess, constantValue });
return equality2;
}

I had the same issue. Thanks for the answer! Very useful!
You wrote this:
caller
{
BinaryExpression expression = null;
ParameterExpression parameter = Expression.Parameter(typeof(DataContext.Table), "p");
string columns = "Relation1.Relation2.Column1";
string value = "ABC123";
BinaryExpression exp
= MakeQueryFilter(typeof(DataContext.Table),
columns,value,'Equal', parameter);
expression = expression == null ? exp : Expression.AndAlso(expression, exp);
results = DataContext.Table.Where(
Expression.Lambda<Func<Table, bool>>(expression, parameter));
}
public static BinaryExpression MakeQueryFilter(Type type, string propertyNames, object value, string dataoperator, ParameterExpression parameter)
{
string[] acolumns = columns.Split('.');
var property = typeof (MediaEvent).GetProperty(acolumns[0]);
MemberExpression propertyAccess = Expression.MakeMemberAccess(parameter, property);
Type propertyType = property.PropertyType;
if ((propertyType.IsGenericType) && (propertyType.GetGenericTypeDefinition() == typeof(Nullable)))
propertyType = propertyType.GetGenericArguments()[0];
if (acolumns.Length > 1)
{
for (int i = 1; i < acolumns.Length; i++)
{
propertyAccess = Expression.MakeMemberAccess(propertyAccess, propertyAccess.Type.GetProperty(acolumns[i]));
}
}
object queryvalue = null;
// convert the value appropriately
if (propertyType == typeof(System.Int32))
queryvalue = Convert.ToInt32(value);
if (property.PropertyType == typeof(DateTime))
queryvalue = Convert.ToDateTime(value);
if (property.PropertyType == typeof(Double))
queryvalue = Convert.ToDouble(value);
if (property.PropertyType == typeof(String))
queryvalue = Convert.ToString(value);
if (property.PropertyType == typeof(Guid))
queryvalue = new Guid(value.ToString());
var constantValue = Expression.Constant(queryvalue);
Type[] types = new Type[2];
types.SetValue(typeof(Expression), 0);
types.SetValue(typeof(Expression), 1);
var methodInfo = typeof(Expression).GetMethod(dataoperator, types);
var equality2 = (BinaryExpression)methodInfo.Invoke(null, new object[] { propertyAccess, constantValue });
return equality2;
}
Maybe better if you write this:
public static BinaryExpression MakeQueryFilter<T>(string propertyNames, object value, string dataoperator, ParameterExpression parameter) where T : class
{
string[] props = propertyNames.Split('.');
var property = typeof(T).GetProperty(props[0]);
MemberExpression propertyAccess = Expression.MakeMemberAccess(parameter, property);
if (props.Length > 1)
{
for (int i = 1; i < props.Length; i++)
{
property = propertyAccess.Type.GetProperty(props[i]);
propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
}
}
Type propertyType = property.PropertyType;
if ((propertyType.IsGenericType) && (propertyType.GetGenericTypeDefinition() == typeof(Nullable)))
propertyType = propertyType.GetGenericArguments()[0];
object queryvalue = null;
// convert the value appropriately
if (propertyType == typeof(System.Int32))
queryvalue = Convert.ToInt32(value);
if (property.PropertyType == typeof(DateTime))
queryvalue = Convert.ToDateTime(value);
if (property.PropertyType == typeof(Double))
queryvalue = Convert.ToDouble(value);
if (property.PropertyType == typeof(String))
queryvalue = Convert.ToString(value);
if (property.PropertyType == typeof(Guid))
queryvalue = new Guid(value.ToString());
var constantValue = Expression.Constant(queryvalue);
Type[] types = new Type[2];
types.SetValue(typeof(Expression), 0);
types.SetValue(typeof(Expression), 1);
var methodInfo = typeof(Expression).GetMethod(dataoperator, types);
var equality2 = (BinaryExpression)methodInfo.Invoke(null, new object[] { propertyAccess, constantValue });
return equality2;
}

Related

i am trying to join 3 tables below is the code

var result = (from p in db.push_notifications
join nu in db.notification_recievers on p.id equals nu.push_notification_id
join nt in db.notification_types on p.notification_type_id equals nt.id
where (p.id == pushNotificationId && p.send_criteria == criteria && nu.delete_flag == false && p.delete_flag == false && nt.delete_flag == false)
select new NotificationList
{
conferenceId = p.conference_id,
pushNotificationId = p.id,
notificationId = nt.id,
notificationType = nt.notification_type,
nottificationDate = p.created_dt_tm,
criteria = (int)p.send_criteria,
notificationMessage = p.notification_msg,
userEmail=null,
userInterests = **getInterestNamesByPushNotificationId(p.id)**,
userEvents=null
}).Distinct();
public string getInterestNamesByPushNotificationId(int id)
{
string interests = string.Empty;
var query = from i in db.interests
join pn in db.notification_recievers
on i.id equals pn.interest_id
where pn.push_notification_id == id && pn.delete_flag == false
select new
{
name = i.name
};
foreach (var intr in query.Distinct())
{
if (interests == "")
{
interests = intr.name;
}
else
{
interests = interests + ", " + intr.name;
}
}
return interests;
}
this is throwing me error
LINQ to Entities does not recognize the method 'System.String
getInterestNamesBy PushNotification(Int32)' method, and this method
cannot be translated into a store expression.
The Entity Framework is trying to execute your LINQ clause on the SQL side, obviously there is no equivalent to 'getInterestNamesBy PushNotification(Int32)' from a SQL perspective.
You need to force your select to an Enumerable and then reselect your object using the desired method.
Not ideal but something like this should work - (not tested this so be nice).
var result = (from p in db.push_notifications
join nu in db.notification_recievers on p.id equals nu.push_notification_id
join nt in db.notification_types on p.notification_type_id equals nt.id
where (p.id == pushNotificationId && p.send_criteria == criteria && nu.delete_flag == false && p.delete_flag == false && nt.delete_flag == false)
select new { p=p, nu = nu, nt = nt }).AsEnumerable().Select( x => new NotificationList()
{
conferenceId = x.p.conference_id,
pushNotificationId = x.p.id,
notificationId = x.nt.id,
notificationType = x.nt.notification_type,
nottificationDate = x.p.created_dt_tm,
criteria = (int)x.p.send_criteria,
notificationMessage = x.p.notification_msg,
userEmail=null,
userInterests = getInterestNamesByPushNotificationId(x.p.id),
userEvents=null
}).Distinct();
i have done it this way
In my model
using (NotificationService nService = new NotificationService())
{
modelView = nService.DetailsOfNotifications(pushNotificationId, criteriaId).Select(x => new NotificationViewModelUI(x.conferenceId, x.pushNotificationId, x.notificationId, x.notificationType, x.nottificationDate, x.criteria, x.notificationMessage, x.userEmail, nService.getInterestNamesByPushNotificationId(x.pushNotificationId), nService.getIEventTitlesByPushNotificationId(x.pushNotificationId))).ToList();
}
public NotificationViewModelUI(int conferenceId, int pushNotificationId, int notificationId, string notificationType, DateTime dateTime, int criteria, string nMessage, string emailId = null, string interestNames = null, string eventTitles = null)
{
this.conferenceId = conferenceId;
this.pushNotificationId = pushNotificationId;
this.notificationId = notificationId;
this.notificationType = notificationType;
this.notificationDate = dateTime;
this.sendCriteria = (NotificationCriteria)criteria;
this.notificationMessage = nMessage;
this.emailId = NotificationCriteria.SpecificUser.Description() +"... "+ emailId;
this.interestNames = NotificationCriteria.UserByInterests.Description() + "... " + interestNames;
this.eventTitles = NotificationCriteria.UserByEvents.Description() + "... " + eventTitles;
}

LINQ query fails with nullable variable ormlite

I'm trying to write following LINQ query using ServiceStack Ormlite.
dbConn.Select<Product>(p => p.IsActive.HasValue && p.IsActive.Value)
Here, Product is my item class and "IsActive" is Nullable Bool property in that class. When this line executes it always throws "InvalidOperationException" with the message
variable 'p' of type '' referenced from scope '', but it is not defined
I tried different variants as following but still same exception result
dbConn.Select<Product>(p => p.IsActive.HasValue == true && p.IsActive.Value == true)
dbConn.Select<Product>(p => p.IsActive != null && p.IsActive.Value == true)
But if I just write
dbConn.Select<Product>(p => p.IsActive.HasValue)
then it works.
I'm puzzled what is the problem? Is this servicestack ormlite issue?
My answer can handle Nullable value like "value" and "HasValue" with servicestack ormlite. And But also with datetime nullable ,like 'createdate.value.Year'.
you must change two place.
modify VisitMemberAccess method:
protected virtual object VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null)
{
if (m.Member.DeclaringType.IsNullableType())
{
if (m.Member.Name == nameof(Nullable<bool>.Value))
return Visit(m.Expression);
if (m.Member.Name == nameof(Nullable<bool>.HasValue))
{
var doesNotEqualNull = Expression.NotEqual(m.Expression, Expression.Constant(null));
return Visit(doesNotEqualNull); // Nullable<T>.HasValue is equivalent to "!= null"
}
throw new ArgumentException(string.Format("Expression '{0}' accesses unsupported property '{1}' of Nullable<T>", m, m.Member));
}
if (m.Member.DeclaringType == typeof(DateTime))
{
var ExpressionInfo = m.Expression as MemberExpression;
if (ExpressionInfo.Member.DeclaringType.IsNullableType())
{
if (ExpressionInfo.Member.Name == nameof(Nullable<bool>.Value))
{
var modelType = (ExpressionInfo.Expression as MemberExpression).Expression.Type;
var tableDef = modelType.GetModelDefinition();
var columnName = (ExpressionInfo.Expression as MemberExpression).Member.Name;
var QuotedColumnName = GetQuotedColumnName(tableDef, columnName);
if (m.Member.Name == "Year")
{
return new PartialSqlString(string.Format("DATEPART(yyyy,{0})", QuotedColumnName));
}
if (m.Member.Name == "Month")
return new PartialSqlString(string.Format("DATEPART(mm,{0})", QuotedColumnName));
}
if (ExpressionInfo.Member.Name == nameof(Nullable<bool>.HasValue))
{
var doesNotEqualNull = Expression.NotEqual(ExpressionInfo.Expression, Expression.Constant(null));
return Visit(doesNotEqualNull); // Nullable<T>.HasValue is equivalent to "!= null"
}
}
else
{
var modelType = ExpressionInfo.Expression.Type;
var tableDef = modelType.GetModelDefinition();
var columnName = ExpressionInfo.Member.Name;
var QuotedColumnName = GetQuotedColumnName(tableDef, columnName);
if (m.Member.Name == "Year")
return new PartialSqlString(string.Format("DATEPART(yyyy,{0})", QuotedColumnName));
if (m.Member.Name == "Month")
return new PartialSqlString(string.Format("DATEPART(mm,{0})", QuotedColumnName));
}
}
if (m.Expression.NodeType == ExpressionType.Parameter || m.Expression.NodeType == ExpressionType.Convert)
{
var propertyInfo = (PropertyInfo)m.Member;
var modelType = m.Expression.Type;
if (m.Expression.NodeType == ExpressionType.Convert)
{
var unaryExpr = m.Expression as UnaryExpression;
if (unaryExpr != null)
{
modelType = unaryExpr.Operand.Type;
}
}
var tableDef = modelType.GetModelDefinition();
if (propertyInfo.PropertyType.IsEnum)
return new EnumMemberAccess(
GetQuotedColumnName(tableDef, m.Member.Name), propertyInfo.PropertyType);
return new PartialSqlString(GetQuotedColumnName(tableDef, m.Member.Name));
}
}
var member = Expression.Convert(m, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
return getter();
}
modify VisitLambda method :
protected virtual object VisitLambda(LambdaExpression lambda)
{
if (lambda.Body.NodeType == ExpressionType.MemberAccess && sep == " ")
{
MemberExpression m = lambda.Body as MemberExpression;
if (m.Expression != null)
{
string r = VisitMemberAccess(m).ToString();
if (m.Member.DeclaringType.IsNullableType())
return r;
return string.Format("{0}={1}", r, GetQuotedTrueValue());
}
}
return Visit(lambda.Body);
}
This is nature of the Linq. In order to achieve what you need, you will need to use two where closes:
dbConn.Where<Product>(p => p.IsActive.HasValue).Where(p=>p.Value==true);

Mock same method in a test using Moq with different linq expressions

I have a repository with this method
public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>,
IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "");
I have a method that is using it as such to get a user to work on
var user = this.applicationUserRepository.Get(x => x.EmailAddress == userName).FirstOrDefault();
It then calls the same method with a different expression further in the method as follows to check the user working on the first user
var changingUser = this.applicationUserRepository.Get(x => x.Id == changingUserId).FirstOrDefault();
I am trying to mock the repository with two setups to call it twice in the same test.
string emailAddress = "myaddresss#test.com";
int changingUserId = 5;
var targetUsers = new List<ApplicationUser>
{
new ApplicationUser { Id = 1, Password = "Testing123", EmailAddress = emailAddress }
};
// Setup the user that will be modified to be found
var mockApplicationUserRepository = new Mock<IRepository<ApplicationUser>>();
mockApplicationUserRepository
.Setup(aur => aur.Get(x => x.EmailAddress == userName, null, string.Empty))
.Returns(targetUsers.AsEnumerable());
// Set up to query the changing user to not be found
mockApplicationUserRepository
.Setup(aur2 => aur2.Get(x => x.Id == changingUserId, null, string.Empty))
.Returns(new List<ApplicationUser>().AsEnumerable()); // Empty list
Even though the second call will never be hit, for this test, I want to learn how to set this up.
When I run the test the first call
var user = this.applicationUserRepository.Get(x => x.EmailAddress == userName).FirstOrDefault();
I get null
If I change the mock to have
It.IsAny<Expression<Func<ApplicationUser, bool>>>()
I get the expected user back.
I can not figure out how I will set the two calls up so it will know which expression to use. Any help will be appreciated.
The problem is that Moq does not compare the expressions you use (only reference equality).
Using this little helper class:
public static class LambdaCompare
{
public static bool Eq<TSource, TValue>(
Expression<Func<TSource, TValue>> x,
Expression<Func<TSource, TValue>> y)
{
return ExpressionsEqual(x, y, null, null);
}
public static Expression<Func<Expression<Func<TSource, TValue>>, bool>> Eq<TSource, TValue>(Expression<Func<TSource, TValue>> y)
{
return x => ExpressionsEqual(x, y, null, null);
}
private static bool ExpressionsEqual(Expression x, Expression y, LambdaExpression rootX, LambdaExpression rootY)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
var valueX = TryCalculateConstant(x);
var valueY = TryCalculateConstant(y);
if (valueX.IsDefined && valueY.IsDefined)
return ValuesEqual(valueX.Value, valueY.Value);
if (x.NodeType != y.NodeType
|| x.Type != y.Type) return false;
if (x is LambdaExpression)
{
var lx = (LambdaExpression) x;
var ly = (LambdaExpression) y;
var paramsX = lx.Parameters;
var paramsY = ly.Parameters;
return CollectionsEqual(paramsX, paramsY, lx, ly) && ExpressionsEqual(lx.Body, ly.Body, lx, ly);
}
if (x is MemberExpression)
{
var mex = (MemberExpression) x;
var mey = (MemberExpression) y;
return Equals(mex.Member, mey.Member) && ExpressionsEqual(mex.Expression, mey.Expression, rootX, rootY);
}
if (x is BinaryExpression)
{
var bx = (BinaryExpression) x;
var by = (BinaryExpression) y;
return bx.Method == #by.Method && ExpressionsEqual(bx.Left, #by.Left, rootX, rootY) &&
ExpressionsEqual(bx.Right, #by.Right, rootX, rootY);
}
if (x is ParameterExpression)
{
var px = (ParameterExpression) x;
var py = (ParameterExpression) y;
return rootX.Parameters.IndexOf(px) == rootY.Parameters.IndexOf(py);
}
if (x is MethodCallExpression)
{
var cx = (MethodCallExpression)x;
var cy = (MethodCallExpression)y;
return cx.Method == cy.Method
&& ExpressionsEqual(cx.Object, cy.Object, rootX, rootY)
&& CollectionsEqual(cx.Arguments, cy.Arguments, rootX, rootY);
}
throw new NotImplementedException(x.ToString());
}
private static bool ValuesEqual(object x, object y)
{
if (ReferenceEquals(x, y))
return true;
if (x is ICollection && y is ICollection)
return CollectionsEqual((ICollection) x, (ICollection) y);
return Equals(x, y);
}
private static ConstantValue TryCalculateConstant(Expression e)
{
if (e is ConstantExpression)
return new ConstantValue(true, ((ConstantExpression) e).Value);
if (e is MemberExpression)
{
var me = (MemberExpression) e;
var parentValue = TryCalculateConstant(me.Expression);
if (parentValue.IsDefined)
{
var result =
me.Member is FieldInfo
? ((FieldInfo) me.Member).GetValue(parentValue.Value)
: ((PropertyInfo) me.Member).GetValue(parentValue.Value);
return new ConstantValue(true, result);
}
}
if (e is NewArrayExpression)
{
var ae = ((NewArrayExpression) e);
var result = ae.Expressions.Select(TryCalculateConstant);
if (result.All(i => i.IsDefined))
return new ConstantValue(true, result.Select(i => i.Value).ToArray());
}
return default(ConstantValue);
}
private static bool CollectionsEqual(IEnumerable<Expression> x, IEnumerable<Expression> y, LambdaExpression rootX, LambdaExpression rootY)
{
return x.Count() == y.Count()
&& x.Select((e, i) => new {Expr = e, Index = i})
.Join(y.Select((e, i) => new { Expr = e, Index = i }),
o => o.Index, o => o.Index, (xe, ye) => new { X = xe.Expr, Y = ye.Expr })
.All(o => ExpressionsEqual(o.X, o.Y, rootX, rootY));
}
private static bool CollectionsEqual(ICollection x, ICollection y)
{
return x.Count == y.Count
&& x.Cast<object>().Select((e, i) => new { Expr = e, Index = i })
.Join(y.Cast<object>().Select((e, i) => new { Expr = e, Index = i }),
o => o.Index, o => o.Index, (xe, ye) => new { X = xe.Expr, Y = ye.Expr })
.All(o => Equals(o.X, o.Y));
}
private struct ConstantValue
{
public ConstantValue(bool isDefined, object value) : this()
{
IsDefined = isDefined;
Value = value;
}
public bool IsDefined { get; private set; }
public object Value { get; private set; }
}
}
you can setup your mock like this:
Expression<Func<ApplicationUser, bool>> expr = x => x.EmailAddress == emailAddress;
var mockApplicationUserRepository = new Mock<IRepository<ApplicationUser>>();
mockApplicationUserRepository
.Setup(aur => aur.Get(It.Is<Expression<Func<ApplicationUser, bool>>>(x => LambdaCompare.Eq(x, expr)), null, string.Empty))
.Returns(targetUsers.AsEnumerable());
Just adding a comment to this old answer (though quite useful code)
This version of LambdaCompare class is missing a case to compare an UnaryExpression, something which is included in the code in the original post.
Use that code instead of this (I learnt the hard way...)

How to compare only date part with linq expression?

I just want to make Column Filter for grid view. Simple I just want to filter grid view column with some extra stuff. Here I have created one IQueryable that returns queryable result.
Here is my code :
------------------------------------------Updated------------------------------------------------
public static IQueryable<T> FilterForColumn<T>(this IQueryable<T> queryable, string colName, string searchText)
{
if (colName != null && searchText != null)
{
var parameter = Expression.Parameter(typeof(T), "m");
var propertyExpression = Expression.Property(parameter, colName);
System.Linq.Expressions.ConstantExpression searchExpression = null;
System.Reflection.MethodInfo containsMethod = null;
System.Linq.Expressions.MethodCallExpression body = null;
System.Nullable<DateTime> nextDate = null;
Expression ex1 = null;
Expression ex2 = null;
switch (colName)
{
case "JobID":
case "FileSize":
case "TotalFileSize":
Int64? size = Convert.ToInt64(searchText);
searchExpression = Expression.Constant(searchText);
containsMethod = typeof(Int64?).GetMethod("Equals", new[] { typeof(Int64?) });
body = Expression.Call(propertyExpression, containsMethod, searchExpression);
break;
case "PublishDate":
case "Birth_date":
case "Anniversary_date":
case "Profile_Updated_datetime":
case "CompletedOn":
DateTime? currentDate = DateTime.ParseExact(searchText, "dd/MM/yyyy", null);
nextDate = currentDate.Value.AddDays(1);
ex1 = Expression.GreaterThanOrEqual(propertyExpression, Expression.Constant(currentDate));
ex2 = Expression.LessThan(propertyExpression, Expression.Constant(nextDate));
body = Expression.AndAlso(ex1, ex2);
break;
case "Created_datetime":
case "Reminder_Date":
case "News_date":
case "thought_date":
case "SubscriptionDateTime":
case "Register_datetime":
case "CreatedOn":
DateTime dt1 = DateTime.ParseExact(searchText, "dd/MM/yyyy", null);
nextDate = currentDate.Value.AddDays(1);
ex1 = Expression.GreaterThanOrEqual(propertyExpression, Expression.Constant(currentDate));
ex2 = Expression.LessThan(propertyExpression, Expression.Constant(nextDate));
body = Expression.AndAlso(ex1, ex2);
break;
default :
searchExpression = Expression.Constant(searchText);
containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
body = Expression.Call(propertyExpression, containsMethod, searchExpression);
break;
}
var predicate = Expression.Lambda<Func<T, bool>>(body, new[] { parameter });
return queryable.Where(predicate);
}
else
{
return queryable;
}
}
here this solution gives some compile time error :
Error 9 Cannot implicitly convert type 'System.Linq.Expressions.BinaryExpression' to 'System.Linq.Expressions.MethodCallExpression' F:\EasyWeb\App_Code\Helper.cs 47 28 F:\EasyWeb\
Here at case of DateTime I just want to filter DateTime column with only Date not whole DATETIME value.
Here I just put required things :
SELECT [t8].[Id], [t8].[Title], [t8].[value] AS [Publisher], [t8].[value2] AS [ToUser], [t8].[Sent_Datetime] AS [PublishDate], [t8].[IsFileAttached] AS [IsFile], [t8].[Category_name] AS [CategoryName], [t8].[value3] AS [status_name], [t8].[value4] AS [Group_name], [t8].[TotalFileSize] AS [FileSize]
FROM (
SELECT [t0].[Id], [t0].[Title], (
SELECT TOP (1) [t3].[value]
FROM (
SELECT ([t2].[First_name] + ' ') + [t2].[Last_name] AS [value], [t2].[Id]
FROM [dbo].[tbl_User_master] AS [t2]
) AS [t3]
WHERE [t3].[Id] = [t0].[User_id]
) AS [value], (
SELECT TOP (1) [t5].[value]
FROM (
SELECT ([t4].[First_name] + ' ') + [t4].[Last_name] AS [value], [t4].[Id]
FROM [dbo].[tbl_User_master] AS [t4]
) AS [t5]
WHERE ([t5].[Id]) = [t0].[ToUser_id]
) AS [value2], [t0].[Sent_Datetime], [t0].[IsFileAttached], [t1].[Category_name], (
SELECT TOP (1) [t6].[status_name]
FROM [dbo].[tbl_status_master] AS [t6]
WHERE ([t6].[Id]) = [t0].[status_id]
) AS [value3], (
SELECT TOP (1) [t7].[Group_name]
FROM [dbo].[tbl_Group_master] AS [t7]
WHERE ([t7].[Id]) = [t0].[group_id]
) AS [value4], [t0].[TotalFileSize], [t0].[ToUser_id], [t0].[User_id]
FROM [dbo].[tbl_Post_History] AS [t0]
INNER JOIN [dbo].[tbl_Category_master] AS [t1] ON [t0].[Category_id] = [t1].[Id]
) AS [t8]
WHERE (CAST(CONVERT(CHAR(10), [t8].[Sent_Datetime], 102) AS DATETIME) = '12/24/2013' ) AND (([t8].[ToUser_id] = 3) OR ([t8].[ToUser_id] IS NULL)) AND ([t8].[User_id] <> 3)
ORDER BY [t8].[Sent_Datetime] DESC
Here this shows required output or logic that we will do with queryable.
But, here one drawback occurs while this method Equals check whole Date Time.
Is that possible with this method to force only check Date Part from column?
------------------------------------2-Updated-----------------------------------------
for overwhelming this problem i use this technique :
public static IQueryable<T> FilterForColumn<T>(this IQueryable<T> queryable, string colName, string searchText)
{
if (colName != null && searchText != null)
{
var parameter = Expression.Parameter(typeof(T), "m");
var propertyExpression = Expression.Property(parameter, colName);
System.Linq.Expressions.ConstantExpression searchExpression = null;
System.Reflection.MethodInfo containsMethod = null;
System.Linq.Expressions.BinaryExpression body = null;
DateTime? currentDate = null;
DateTime? nextDate = null;
Expression ex1 = null;
Expression ex2 = null;
switch (colName)
{
case "JobID":
case "FileSize":
case "TotalFileSize":
Int64? size = Convert.ToInt64(searchText);
searchExpression = Expression.Constant(searchText);
containsMethod = typeof(Int64?).GetMethod("Equals", new[] { typeof(Int64?) });
//body = Expression.Call(propertyExpression, containsMethod, searchExpression);
break;
case "PublishDate":
case "Birth_date":
case "Anniversary_date":
case "Profile_Updated_datetime":
case "CompletedOn":
currentDate = DateTime.ParseExact(searchText, "dd/MM/yyyy", null);
nextDate = currentDate.Value.AddDays(1);
ex1 = Expression.GreaterThanOrEqual(propertyExpression, Expression.Constant(currentDate));
ex2 = Expression.LessThan(propertyExpression, Expression.Constant(nextDate));
body = Expression.AndAlso(ex1, ex2);
break;
case "Created_datetime":
case "Reminder_Date":
case "News_date":
case "thought_date":
case "SubscriptionDateTime":
case "Register_datetime":
case "CreatedOn":
currentDate = DateTime.ParseExact(searchText, "dd/MM/yyyy", null);
nextDate = currentDate.Value.AddDays(1);
ex1 = Expression.GreaterThanOrEqual(propertyExpression, Expression.Constant(currentDate));
ex2 = Expression.LessThan(propertyExpression, Expression.Constant(nextDate));
body = Expression.AndAlso(ex1, ex2);
break;
default :
searchExpression = Expression.Constant(searchText);
containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
//body = Expression.Call(propertyExpression, containsMethod, searchExpression);
break;
}
var predicate = Expression.Lambda<Func<T, bool>>(body, new[] { parameter });
return queryable.Where(predicate);
}
else
{
return queryable;
}
}
it's give me run time error like :
Server Error in '/EasyWeb' Application.
The binary operator GreaterThanOrEqual is not defined for the types 'System.Nullable`1[System.DateTime]' and 'System.DateTime'.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The binary operator GreaterThanOrEqual is not defined for the types 'System.Nullable`1[System.DateTime]' and 'System.DateTime'.
Source Error:
Line 44: currentDate = DateTime.ParseExact(searchText, "dd/MM/yyyy", null);
Line 45: nextDate = currentDate.Value.AddDays(1);
Line 46: ex1 = Expression.GreaterThanOrEqual(propertyExpression, Expression.Constant(currentDate));
Line 47: ex2 = Expression.LessThan(propertyExpression, Expression.Constant(nextDate));
Line 48: body = Expression.AndAlso(ex1, ex2);
Source File: f:\EasyWeb\App_Code\Helper.cs Line: 46
Stack Trace:
[InvalidOperationException: The binary operator GreaterThanOrEqual is not defined for the types 'System.Nullable`1[System.DateTime]' and 'System.DateTime'.]
System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow(ExpressionType binaryType, String name, Expression left, Expression right, Boolean liftToNull) +752213
System.Linq.Expressions.Expression.GetComparisonOperator(ExpressionType binaryType, String opName, Expression left, Expression right, Boolean liftToNull) +221
System.Linq.Expressions.Expression.GreaterThanOrEqual(Expression left, Expression right, Boolean liftToNull, MethodInfo method) +53
System.Linq.Expressions.Expression.GreaterThanOrEqual(Expression left, Expression right) +8
Helper.FilterForColumn(IQueryable`1 queryable, String colName, String searchText) in f:\EasyWeb\App_Code\Helper.cs:46
Admin_Post_History.FillGrid(String CommandName, String ColumnName, String SearchText) in f:\EasyWeb\Admin\Post_History.aspx.cs:79
Admin_Post_History.btnsearch_Click(Object sender, EventArgs e) in f:\EasyWeb\Admin\Post_History.aspx.cs:2375
System.Web.UI.WebControls.Button.OnClick(EventArgs e) +111
System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +110
System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +10
System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +13
System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +36
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1565
Version Information: Microsoft .NET Framework Version:2.0.50727.3053; ASP.NET Version:2.0.50727.3053
-------------------------------- 3 Update-------------------------------------------
here is what i'm querying :
case "Inbox":
lbl_disply.Text = "Inbox";
lbut_showinbox.Font.Bold = true;
lbut_showoutbox.Font.Bold = false;
lbut_showdraffs.Font.Bold = false;
lbut_showtrash.Font.Bold = false;
var query1 = db.Posts.Where(p => (p.ToUser_id.Equals(user_id) || p.ToUser_id.Equals(null)) && p.User_id != user_id).OrderByDescending(p=>p.Sent_Datetime).Select(p => new
{
Id = p.Id,
Title = p.Title,
Publisher = db.Users.Where(u => u.Id.Equals(p.User_id)).Select(u => u.First_name + ' ' + u.Last_name).FirstOrDefault(),
ToUser = db.Users.Where(u => u.Id.Equals(p.ToUser_id)).Select(u => u.First_name + ' ' + u.Last_name).FirstOrDefault(),
PublishDate = p.Sent_Datetime,
IsFile = p.IsFileAttached,
CategoryName = db.Categories.Where(c => c.Id.Equals(p.Category_id)).Select(c => c.Category_name).FirstOrDefault(),
status_name = db.Status.Where(s => s.Id.Equals(p.status_id)).Select(s => s.status_name).FirstOrDefault(),
Group_name = db.Groups.Where(g => g.Id.Equals(p.group_id)).Select(g => g.Group_name).FirstOrDefault(),
FileSize = p.TotalFileSize
}).FilterForColumn(ColumnName, SearchText).ToList();
at grid view filling
DataSet myDataSet = new DataSet();
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("Id", typeof(int)));
dt.Columns.Add(new DataColumn("IsRead", typeof(bool)));
dt.Columns.Add(new DataColumn("IsImp", typeof(bool)));
dt.Columns.Add(new DataColumn("Title", typeof(string)));
dt.Columns.Add(new DataColumn("Publisher", typeof(string)));
dt.Columns.Add(new DataColumn("ToUser", typeof(string)));
dt.Columns.Add(new DataColumn("PublishDate", typeof(DateTime?)));
dt.Columns.Add(new DataColumn("IsFile", typeof(bool)));
dt.Columns.Add(new DataColumn("CategoryName", typeof(string)));
dt.Columns.Add(new DataColumn("status_name", typeof(string)));
dt.Columns.Add(new DataColumn("Group_name", typeof(string)));
dt.Columns.Add(new DataColumn("FileSize", typeof(string)));
foreach (var item in query1)
i declared this PublishDate as typeof(DateTime?) but this gives me run time error like :
Server Error in '/EasyWeb' Application.
DataSet does not support System.Nullable<>.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NotSupportedException: DataSet does not support System.Nullable<>.
Source Error:
Line 101: dt.Columns.Add(new DataColumn("Publisher", typeof(string)));
Line 102: dt.Columns.Add(new DataColumn("ToUser", typeof(string)));
Line 103: dt.Columns.Add(new DataColumn("PublishDate", typeof(DateTime?)));
Line 104: dt.Columns.Add(new DataColumn("IsFile", typeof(bool)));
Line 105: dt.Columns.Add(new DataColumn("CategoryName", typeof(string)));
Source File: f:\EasyWeb\Admin\Post_History.aspx.cs Line: 103
Stack Trace:
[NotSupportedException: DataSet does not support System.Nullable<>.]
System.Data.DataColumn..ctor(String columnName, Type dataType, String expr, MappingType type) +4826536
System.Data.DataColumn..ctor(String columnName, Type dataType) +12
Admin_Post_History.FillGrid(String CommandName, String ColumnName, String SearchText) in f:\EasyWeb\Admin\Post_History.aspx.cs:103
Admin_Post_History.Page_Load(Object sender, EventArgs e) in f:\EasyWeb\Admin\Post_History.aspx.cs:59
System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +14
System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +35
System.Web.UI.Control.OnLoad(EventArgs e) +99
System.Web.UI.Control.LoadRecursive() +50
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +627
Version Information: Microsoft .NET Framework Version:2.0.50727.3053; ASP.NET Version:2.0.50727.3053
---------------------------------4 Update----------------------------------------
i check my dbml file and this is screen shot for verify that it's nullable
To achieve what you want you can use predicate
WHERE Sent_Datetime >= '20131224' AND Sent_Datetime < '20131225'
so you can use this expression
DateTime currentDate = DateTime.ParseExact("06/01/2008", "dd/MM/yyyy", null);
DateTime nextDate = currentDate.AddDays(1);
Expression ex1 = Expression.GreaterThanOrEqual(
propertyExpression, Expression.Constant(currentDate));
Expression ex2 = Expression.LessThan(
propertyExpression, Expression.Constant(nextDate));
Expression body = Expression.AndAlso(ex1, ex2);
var predicate = Expression.Lambda<Func<T, bool>>(body, new[] { parameter });
Of course, here you have sargable predicate.
Update
I've created the complete example for you:
public static IQueryable<T> FilterForColumn<T>(this IQueryable<T> queryable, string colName, string searchText)
{
if (colName != null && searchText != null)
{
var parameter = Expression.Parameter(typeof(T), "m");
var propertyExpression = Expression.Property(parameter, colName);
System.Linq.Expressions.ConstantExpression searchExpression = null;
System.Reflection.MethodInfo containsMethod = null;
// this must be of type Expression to accept different type of expressions
// i.e. BinaryExpression, MethodCallExpression, ...
System.Linq.Expressions.Expression body = null;
Expression ex1 = null;
Expression ex2 = null;
switch (colName)
{
case "JobID":
case "FileSize":
case "TotalFileSize":
Int64? size = Convert.ToInt64(searchText);
searchExpression = Expression.Constant(searchText);
containsMethod = typeof(Int64?).GetMethod("Equals", new[] { typeof(Int64?) });
body = Expression.Call(propertyExpression, containsMethod, searchExpression);
break;
// section for DateTime? properties
case "PublishDate":
case "Birth_date":
case "Anniversary_date":
case "Profile_Updated_datetime":
case "CompletedOn":
DateTime currentDate = DateTime.ParseExact(searchText, "dd/MM/yyyy", null);
DateTime nextDate = currentDate.AddDays(1);
ex1 = Expression.GreaterThanOrEqual(propertyExpression, Expression.Constant(currentDate, typeof(DateTime?)));
ex2 = Expression.LessThan(propertyExpression, Expression.Constant(nextDate, typeof(DateTime?)));
body = Expression.AndAlso(ex1, ex2);
break;
// section for DateTime properties
case "Created_datetime":
case "Reminder_Date":
case "News_date":
case "thought_date":
case "SubscriptionDateTime":
case "Register_datetime":
case "CreatedOn":
DateTime currentDate = DateTime.ParseExact(searchText, "dd/MM/yyyy", null);
DateTime nextDate = currentDate.AddDays(1);
ex1 = Expression.GreaterThanOrEqual(propertyExpression, Expression.Constant(currentDate));
ex2 = Expression.LessThan(propertyExpression, Expression.Constant(nextDate));
body = Expression.AndAlso(ex1, ex2);
break;
default :
searchExpression = Expression.Constant(searchText);
containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
body = Expression.Call(propertyExpression, containsMethod, searchExpression);
break;
}
var predicate = Expression.Lambda<Func<T, bool>>(body, new[] { parameter });
return queryable.Where(predicate);
}
else
{
return queryable;
}
}
You can use the SqlFunctions.DateDiff function specifying "day" value as datepart parameter.
var result = entities
.Where(item =>
SqlFunctions.DateDiff("day", item.DateProperty, dateToCompare) == 0);

Generic Orderby LINQ

I have a generic repository method to enable server side paging:
public virtual IEnumerable<T> GetList(out int totalPages, Expression<Func<T, bool>> filter = null,
Expression<Func<T, object>> orderby = null,
bool ascending = true,
string includeProperties = "",
int pageSize = 10, int pageNumber = 1)
{
IQueryable<T> query = c.Set<T>();
if (filter != null)
{
query = query.Where(filter);
}
query = includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (current, includeProp) => current.Include(includeProp));
if (orderby != null)
{
query = #ascending ? query.OrderBy(#orderby) : query.OrderByDescending(#orderby);
}
else
{
query = #ascending ? query.OrderBy(o => o.Id) : query.OrderByDescending(o => o.Id);
}
//totalPages = (int) Math.Ceiling((double)(Queryable.Count(query) / pageSize));
totalPages = 1;
if (pageSize > 0)
{
var skip = 0;
var take = pageSize;
if (pageNumber > 1)
{
skip = (pageNumber - 1) * pageSize;
take = pageSize + skip;
}
query = query.Take(take);
query = query.Skip(skip);
}
return query.ToList();
}
T is a base class inherited by my entities
I call this repository method from my service. and i call my service from my controller.
Now from my controller how do i create my orderby expression so that i can pass any column name?
What i've tried so far (found on stackoverflow):
var _OrderByProperty = typeof(Year).GetProperty("Id");
var _OrderByParameter = Expression.Parameter(typeof(Year), "x");
var _OrderByBody = Expression.Property(_OrderByParameter, _OrderByProperty.Name);
var _OrderByConverted = Expression.Convert(_OrderByBody, typeof(Object));
var _OrderByLambda = Expression.Lambda<Func<Year, object>>
(_OrderByConverted, _OrderByParameter);
var list = s.GetList(orderby: _OrderByLambda, totalPages: out totalPages, pageNumber: pageNumber, pageSize: pageSize, ascending: isAsc);
i'm getting the error
Unable to cast the type 'System.Int32' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.

Resources