Here is my original select query which works for one field "SurveyName":
public static IQueryable Select(this IQueryable source, string selector, params object[] values)
{
LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, null, "SurveyName", values); //SurveyName is hard coded here as example
return source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "Select",
new Type[] { source.ElementType, lambda.Body.Type },
source.Expression, Expression.Quote(lambda)));
}
I want to add second lambda expression and I tried below:
public static IQueryable Select(this IQueryable source, string selector, params object[] values)
{
LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, null, "SurveyName", values); //SurveyName is hard coded as example
LambdaExpression lambda2 = DynamicExpression.ParseLambda(source.ElementType, null, "SurveyHeaderName", values); //SurveyHeaderName is hard coded as example
return source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "Select",
new Type[] { source.ElementType, lambda.Body.Type, lambda2.Body.Type },
source.Expression, Expression.Quote(lambda)));
}
Which give me
No generic method 'Select' on type 'System.Linq.Queryable' is
compatible with the supplied type arguments and arguments. No type
arguments should be provided if the method is non-generic
How to achieve add additional lambda expression (I mean additional field Select SurveyName, SurveyHeaderName)
Hear is "lambda" screen shot in debug
As giving more details, why I use above code, here is frontend part:
public class SurveyQuestionSurveyHeaderSurvey
{
public SurveyQuestion SurveyQuestion { get; set; }
public SurveyHeader SurveyHeader { get; set; }
public Survey Survey { get; set; }
public String SurveyName { get; set; }
public String SurveyHeaderName { get; set; }
}
public IEnumerable<SurveyQuestionReportViewModel> SurveyQuestionReportExcel(IDataTablesRequest dataTablesRequest, out int totalCount)
{
var res0 = from sq in _uow.Repository<SurveyQuestion>().Queryable()
join s in _uow.Repository<Survey>().Queryable()
on sq.SurveyHeader.SurveyId equals s.SurveyId
select new SurveyQuestionSurveyHeaderSurvey { SurveyQuestion = sq, Survey = s, SurveyHeader = sq.SurveyHeader, SurveyName = s.Name };
List<Expression<Func<object, bool>>> _expression;
List<Expression<Func<object, object>>> _includes;
_includes = new List<Expression<Func<object, object>>>();
_expression = new List<Expression<Func<object, bool>>>();
Func<IQueryable<object>, IOrderedQueryable<object>> _orderBy;
if (dataTablesRequest.GetSearchListParameter() != null)
_expression.Add(DynamicQueryable.WhereLambdaExpression<object>(dataTablesRequest.GetSearchListParameter()));
if (dataTablesRequest.GetOrderByParameter() != null)
_orderBy = p => p.OrderBy(dataTablesRequest.GetOrderByParameter());
else
_orderBy = p => p.OrderBy("ID Desc");
var fields = String.Join(",", dataTablesRequest.Columns.Select(x => x.Data).ToArray()).TrimEnd(',');
fields = "SurveyName";
var toto = res0.Select(fields, null, _expression, _orderBy, _includes, dataTablesRequest.Start, dataTablesRequest.Length);
var result = res0.Include(p => p.SurveyHeader.Survey).Where(x => x.SurveyHeader.SurveyId == 3074)
.Select(x => new SurveyQuestionReportViewModel
{
SurveyQuestionId = x.SurveyQuestion.SurveyQuestionId,
SurveyName = x.SurveyHeader.Survey.Name,
Name = "Genel Sonuç",
HeaderName = x.SurveyHeader.Name,
QuestionName = x.SurveyQuestion.Description,
});
totalCount = 10;
return result;
}
I first build my lambda expression by:
static Expression<Func<T, T>> BuildLambda<T>()
{
var createdType = typeof(T);
var displayValueParam = Expression.Parameter(typeof(T), "Param_0");
var ctor = Expression.New(createdType);
var bindings = new List<MemberAssignment>();
foreach (var propertyInfo in typeof(T).GetProperties())
{
Expression left = Expression.Property(displayValueParam, propertyInfo);
var displayValueProperty = propertyInfo;
bindings.Add(Expression.Bind(displayValueProperty, left));
}
var memberInit = Expression.MemberInit(ctor, bindings);
return
Expression.Lambda<Func<T, T>>(memberInit, displayValueParam);
}
And my select is :
public static IQueryable<T> Select<T>(this IQueryable source, IDataTablesRequest dataTablesRequest, out int totalCount)
{
var lambda = BuildLambda<T>();
var query = source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "Select",
new Type[] { source.ElementType, lambda.Body.Type },
source.Expression
, Expression.Quote((Expression)lambda)
));
if (dataTablesRequest.GetSearchListParameter() != null)
query = query.Where(dataTablesRequest.GetSearchListParameter());
totalCount = query.Count();
if (dataTablesRequest.GetOrderByParameter() != null)
query = query.OrderBy(dataTablesRequest.GetOrderByParameter(), null);
query = query.Skip(dataTablesRequest.Start).Take(dataTablesRequest.Length);
return (IQueryable<T>)query;
}
Related
i am trying to use projection map to map the property values from source to destination as mentioned here in this question Projection mapping looks like as below but getting error near select statement
Error is : The type arguments for the method
'Enumerable.Select<TSource, Tresult>(IEnumerable,
Func<Tsource, int, Tresult>)' cannot be inferred from usage.Try
specifying type arguments explicitly
and below is the code sample
public static IQueryable<TDest> ProjectionMap<TSource, TDest>(IQueryable<TSource> sourceModel)
where TDest : new()
{
var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead);
var destProperties = typeof(TDest).GetProperties().Where(p => p.CanWrite);
var propertyMap = from d in destProperties
join s in sourceProperties on new { d.Name, d.PropertyType } equals new { s.Name, s.PropertyType }
select new { Source = s, Dest = d };
var itemParam = Expression.Parameter(typeof(TSource), "item");
var memberBindings = propertyMap.Select(p => (MemberBinding)Expression.Bind(p.Dest, Expression.Property(itemParam, p.Source)));
var newExpression = Expression.New(typeof(TDest));
var memberInitExpression = Expression.MemberInit(newExpression, memberBindings);
var projection = Expression.Lambda<Func<TSource, TDest>>(memberInitExpression, itemParam);
return sourceModel.Select(projection);
}
and then i am using above method below
private static MechanicalData TransformMechanicalData(MechanicalData sourceMechanicalData, Dictionary<string, MasterSection> masterSectionMappedLibrary)
{
return new MechanicalData()
{
Acoustic = sourceMechanicalData.Acoustic
.Where(a => a != null)
.Select(ProjectionMap<LibraryAcoustic, LibraryAcoustic>(sourceMechanicalData.Acoustic.AsQueryable())).ToList() ?? new(),
}
}
Could any one please let me know where I am doing wrong, many thanks in advance.
Update:
Acoustic = sourceMechanicalData.Acoustic
.Where(a => a != null)
.Select(acoustic => new LibraryAcoustic
{
Id = acoustic.Id,
IsApproved = true,
NoiseCriteria = acoustic.NoiseCriteria,
SourceOfData = acoustic.SourceOfData,
SourceOfDataId = acoustic.SourceOfData.Id,
MasterSection = masterSectionMappedLibrary["Library Acoustic"]
}).ToList() ?? new(),
calling that transformMechanicalData method in below
if (spaceTypeReader.HasRows)
{
while (spaceTypeReader.Read())
{
var id = spaceTypeReader.IsDBNull(0) ? default : Guid.Parse(spaceTypeReader.GetString(0));
var mechanicalDataJson = spaceTypeReader.IsDBNull(1) ? "null" : spaceTypeReader.GetString(1);
var srcMechanicalDataJson = JsonConvert.DeserializeObject<MechanicalData>(mechanicalDataJson);
fixedSpaceTypesMechanicalData[id] = TransformMechanicalData(srcMechanicalDataJson, masterSectionMappedLibrary);
}
}
and mechanical data class be like
public class MechanicalData
{
public List<LibraryAcoustic> Acoustic { get; set; }
.........
}
Update 2:
model for libraryAcoustic
public class LibraryAcoustic
{
public double? NoiseCriteria { get; set; }
[ForeignKey("SourceOfData")]
public Guid? SourceOfDataId { get; set; }
public virtual CodeStandardGuideline SourceOfData { get; set; }
public Guid Id { get; set; }
public MasterSection MasterSection { get; set; }
public bool? IsApproved { get; set; }
}
FROM Model
"Acoustic": [
{
"Id": "d9254132-d11d-48dd-9b74-c0b60d1e4b8a",
"IsApproved": null,
"SourceOfData": {
"Id": "c5bf3585-50b1-4894-8fad-0ac884343935",
"CodeStandardGuidelineType": "GUIDELINE_OR_STANDARD"
},
"MasterSection": null,
"SourceOfDataId": null,
"NoiseCriteria": 1,
}
],
TO model:
"Acoustic": [
{
"Id": "d9254132-d11d-48dd-9b74-c0b60d1e4b8a",
"IsApproved": true,
"SourceOfData": {
"Id": "c5bf3585-50b1-4894-8fad-0ac88434393",
"CodeStandardGuidelineType": "GUIDELINE_OR_STANDARD"
},
"MasterSection": {Name:"test"},
"SourceOfDataId": "c5bf3585-50b1-4894-8fad-0ac884343935",
"NoiseCriteria": 1,
}
],
Test Class update:
SourceOfData sourceOfData = new SourceOfData()
{
Id = new Guid("c5bf3585-50b1-4894-8fad-0ac884343935"),
Name = "test"
};
TestClassA170 testClassA170 = new TestClassA170()
{
Category = "test",
SourceOfData = sourceOfData,
SourceOfDataId = null,
IsApproved = true,
MinOutdoorAirACH = 1,
MinTotalAirACH = 2,
DirectExhaust = DirectExhaust.NO,
PressureRelationship = PressureRelationship.NEGATIVE,
RecirculatedAir = RecirculatedAir.NO,
SpaceFunction = "10"
};
List<TestClassA170> list = new List<TestClassA170>();
list.Add(testClassA170);
You have to create mapping helper class which accespts additionally Dictionary as paraneter:
public static class PropertyMapper<TSource, TDest>
{
private static Expression<Func<TSource Dictionary<string, MasterSection>, TDest>> _mappingExpression;
private static Func<TSource, Dictionary<string, MasterSection>, TDest> _mapper;
static PropertyMapper()
{
_mappingExpression = ProjectionMap();
_mapper = _mappingExpression.Compile();
}
public static Func<TSource, Dictionary<string, MasterSection>, TDest> Mapper => _mapper;
public static string MasterKeyFromClassName(string className)
{
// you have to do that yourself
throw new NotImplementedException();
}
public static Expression<Func<TSource, Dictionary<string, MasterSection>, TDest>> ProjectionMap()
{
var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead);
var destProperties = typeof(TDest).GetProperties().Where(p => p.CanWrite);
var propertyMap =
from d in destProperties
join s in sourceProperties on new { d.Name, d.PropertyType } equals new { s.Name, s.PropertyType }
where d.Name != "MasterSection"
select new { Source = s, Dest = d };
var itemParam = Expression.Parameter(typeof(TSource), "item");
var dictParam = Expression.Parameter(typeof(Dictionary<string, MasterSection>), "dict");
var memberBindings = propertyMap.Select(p => (MemberBinding)Expression.Bind(p.Dest, Expression.Property(itemParam, p.Source))).ToList();
var masterSectionProp = destProperties.FirstOrDefault(s => s.Name == "MasterSection");
if (masterSectionProp != null)
{
Expression<Func<Dictionary<string, MasterSection>, string, MasterSection>> dictRetrievalTemplate = (dict, value) => dict[value];
var masterPropertyBind = Expression.Bind(masterSectionProp, ExpressionReplacer.GetBody(dictRetrievalTemplate, dictParam, Expression.Constant(MasterKeyFromClassName(typeof(TSource).Name)));
memberBindings.Add(masterPropertyBind);
}
var sourceOfDataProp = destProperties.FirstOrDefault(s => s.Name == "SourceOfDataId");
if (sourceOfDataProp != null)
{
memberBindings.Add(Expression.Bind(sourceOfDataProp, Expression.Property(Expression.Property(itemParam, "SourceOfData"), "Id")));
}
var isApprovedProp = destProperties.FirstOrDefault(s => s.Name == "IsApproved");
if (isApprovedProp != null)
{
memberBindings.Add(Expression.Bind(isApprovedProp, Expression.Constant(true)));
}
var newExpression = Expression.New(typeof(TDest));
var memberInitExpression = Expression.MemberInit(newExpression, memberBindings);
var projection = Expression.Lambda<Func<TSource, Dictionary<string, MasterSection>, TDest>>(memberInitExpression, itemParam, dictParam);
return projection;
}
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression exp)
{
if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
return replacement;
return base.Visit(exp);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
.ToDictionary(i => (Expression) lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
}
}
}
Then rewrite your function:
private static Expression<MechanicalData TransformMechanicalData(MechanicalData sourceMechanicalData, Dictionary<string, MasterSection> masterSectionMappedLibrary)
{
return new MechanicalData()
{
Acoustic = sourceMechanicalData.Acoustic
.Where(a => a != null)
.Select(a => PropertyMapper<LibraryAcoustic, LibraryAcoustic>.Mapper(a, masterSectionMappedLibrary)).ToList(),
}
}
*****Scroll down for final working solution*****
All of my Entity Framework models use partials, which implement my own IEntity interface:
public interface IEntity
{
int Status { get; set; }
int ID { get; set; }
}
This allows me to filter any Entity which implements this interface, based on the following function (simplified version):
public static IQueryable<T> FilterByStatus<T>(this IQueryable<T> query, int status) where T : class, IEntity
{
return query.Where(m => m.Status == status);
}
Now I want a function which names all of the properties, which I might want to perform a text query on. Let's say that implementation Foo of IEntity has 2 values (Bar and Baz) that I want to perform queries on.
I currently have:
public static IQueryable<Foo> FooSearch(this Entities context, string query)
{
IQueryable<Foo> result = context.Foo;
if (!String.IsNullOrEmpty(query))
{
result = result.Where(m =>
m.Bar.ToLower().IndexOf(query.ToLower()) >= 0 ||
m.Baz.ToLower().IndexOf(query.ToLower()) >= 0);
}
return result;
}
But I want to set it up in a more generic way. Something like:
public interface IEntity
{
int Status { get; set; }
int ID { get; set; }
string[] QueryableProperties { get; set; }
}
And some kind of implementation like (pseudocode):
public static IQueryable<T> GenericSearch(this IQueryable<T> query, string query) where T : class, IEntity
{
if (!String.IsNullOrEmpty(query))
{
query = query.Where(m =>
m[QueryableProperties[0]].ToLower().IndexOf(query.ToLower()) >= 0 ||
m[QueryableProperties[1]].ToLower().IndexOf(query.ToLower()) >= 0 ||
// .... //
m[QueryableProperties[QueryableProperties.Count - 1]].ToLower().IndexOf(query.ToLower()) >= 0)
}
return query;
}
How can I achieve this?
******Working Solution******
Search function:
public static class SearchFilter
{
private static Expression GetNestedPropertyExpression(Expression expression, string propertyName)
{
Expression body = expression;
foreach (var member in propertyName.Split('.'))
{
body = Expression.PropertyOrField(body, member);
}
return body;
}
private static Expression<Func<T, bool>> GetSearchExpression<T>(string[] propertyNames, string query)
{
var parameterExp = Expression.Parameter(typeof(T), "category");
MethodInfo containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
MethodInfo toLowerMethod = typeof(string).GetMethod("ToLower", Type.EmptyTypes);
List<Expression> methodCalls = new List<Expression>();
foreach (string propertyName in propertyNames)
{
var propertyExp = GetNestedPropertyExpression(parameterExp, propertyName);
var queryValue = Expression.Constant(query.ToLower(), typeof(string));
var toLowerMethodExp = Expression.Call(propertyExp, toLowerMethod);
var containsMethodExp = Expression.Call(toLowerMethodExp, containsMethod, queryValue);
methodCalls.Add(containsMethodExp);
}
var orExp = methodCalls.Aggregate((left, right) => Expression.Or(left, right));
return Expression.Lambda<Func<T, bool>>(orExp, parameterExp);
}
public static IQueryable<T> Search<T>(this IQueryable<T> query, string property) where T : class, IEntity
{
var filterAttributes = typeof(T).GetCustomAttributes(
typeof(FilterableAttribute), true
).FirstOrDefault() as FilterableAttribute;
if (filterAttributes == null) {
return query;
}
var filterableColumns = filterAttributes.FilterableAttributes;
if (filterableColumns == null || filterableColumns.Count() == 0)
{
return query;
}
if (property == null)
{
return query;
}
return query.Where(GetSearchExpression<T>(filterableColumns, property));
}
}
Decorator (example: both a property of my model, and a nested property):
[Filterable(FilterableAttributes = new string[] {
nameof(Foo),
nameof(Bar) + "." + nameof(Models.MyConnectedModel.Baz)
})]
public partial class MyConnectedModel: IEntity
{
}
Nice question :)
Here's how you can do this:
static Expression<Func<T, bool>> GetExpression<T>(string[] propertyNames, string query)
{
var parameterExp = Expression.Parameter(typeof(T), "category");
MethodInfo containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
MethodInfo toLowerMethod = typeof(string).GetMethod("ToLower",Type.EmptyTypes);
List<Expression> methodCalls = new List<Expression>();
foreach (string propertyName in propertyNames)
{
var propertyExp = Expression.Property(parameterExp, propertyName);
var queryValue = Expression.Constant(query.ToLower(), typeof(string));
var toLowerMethodExp = Expression.Call(propertyExp, toLowerMethod);
var containsMethodExp = Expression.Call(toLowerMethodExp, containsMethod, queryValue);
methodCalls.Add(containsMethodExp);
}
var orExp = methodCalls.Aggregate((left, right) => Expression.Or(left, right));
return Expression.Lambda<Func<T, bool>>(orExp, parameterExp);
}
And then you can use it like this (query is an IQueryable<MyEntity>)
query=query.Where(GetExpression<MyEntity>(queryableProperties,"SomeValue"));
IMO, it can be surprisingly easy:
IQueryable<T> GenericSearch<T>(this IQueryable<T> items, string query)
{
var queryableProperties = items
.First()
.GetType()
.GetProperties()
.Where(p => p.PropertyType == typeof(string))
.ToList();
return items.Where(i => queryableProperties.Any(p => ((string)p.GetValue(i)).Contains(query)));
}
This searches all properties of type string in a list of items.
It can be a class method, an extension method, a static function, it's easy to understand and works well. It can be extended to Fields or restricted to some interfaces easily.
Adjust to your liking.
I have the following setup:
public class ProductSpecification {
public string Name { get; set; }
public string Value { get; set; }
}
public class Product {
public IEnumerable<ProductSpecification> Specifications { get; set; }
}
I want to construct the following ExpressionTree in C# programmatically:
p => p.Specifications.Any(s=>s.Name.Contains("name_val") && s.Value.Contains("value_val"))
I've managed to do this so far, but not even close to the end result:
public ExpressionBuilder AndStartsWith(string property, string value)
{
var arguments = new Dictionary<object, Type>
{
{ value, typeof(string)},
{ StringComparison.InvariantCultureIgnoreCase, typeof(StringComparison)}
};
MethodCallExpression methodExpression = GetExpressionMethod(property, "StartsWith", arguments);
_accumulator = Expression.AndAlso(_accumulator, methodExpression);
return this;
}
private MethodCallExpression GetExpressionMethod(string property, string methodName, Dictionary<object, Type> arguments) {
MethodInfo method = typeof(string).GetMethod(methodName, arguments.Values.ToArray());
Expression source = GetExpressionBody(property);
IEnumerable<ConstantExpression> argumentsExpressions = arguments.Select(x => Expression.Constant(x.Key, x.Value));
MethodCallExpression methodExpression = Expression.Call(source, method, argumentsExpressions);
return methodExpression;
}
Could you hint me to a solution?
Hint, you can view the target expression tree you're trying to build by letting the compiler build it for you.
Expression<Func<Product, bool>> expr = p =>
p.Specifications.Any(s=>s.Name.Contains("name_val") && s.Value.Contains("value_val"));
Anyway, here's how you can build that expression:
public Expression<Func<Product, bool>> GenerateProductExpression()
{
var param = Expression.Parameter(typeof(Product), "p");
var nameValue = Expression.Constant("name_val");
var valueValue = Expression.Constant("name_val");
var specifications = Expression.Property(param, "Specifications");
var body =
Expression.Call(typeof(Enumerable), "Any", new[] { typeof(ProductSpecification) },
specifications, GenerateAnyPredicate(nameValue, valueValue)
);
return Expression.Lambda<Func<Product, bool>>(body, param);
}
public Expression<Func<ProductSpecification, bool>> GenerateAnyPredicate(
Expression nameValue, Expression valueValue)
{
var param = Expression.Parameter(typeof(ProductSpecification), "s");
var name = Expression.Property(param, "Name");
var value = Expression.Property(param, "Value");
var body = Expression.AndAlso(
Expression.Call(name, "Contains", null, nameValue),
Expression.Call(value, "Contains", null, valueValue)
);
return Expression.Lambda<Func<ProductSpecification, bool>>(body, param);
}
What is this error :
The parameter 'd' was not bound in the specified LINQ to Entities
query expression
See details :
private static IEnumerable<T> ConnectToDatabase(IQueryable dbSet, ParameterExpression pe, IEnumerable<Expression> expressions,
string orderby, bool desc)
{
// expressions =
Expression body = null;
if (expressions.Any())
{
foreach (Expression expression in expressions)
{
body = ExpressionExtensions.JoinExpressions(body == null, Expression.Or, body, expression);
}
}
if (body == null)
{
Expression left = Expression.Property(pe, "ID");
Expression right = Expression.Constant(-1);
body = Expression.NotEqual(left, right);
}
IQueryable<T> results;
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new[] { dbSet.ElementType },
dbSet.Expression,
Expression.Lambda<Func<T, bool>>(body, new[] { pe }));
var ModelType = typeof(T);
pe = Expression.Parameter(ModelType, "x");
var propertyInfoOrderBy = GetPropertyInfo(orderby);
var propertyAccess = Expression.MakeMemberAccess(pe, propertyInfoOrderBy);
var orderByExp = Expression.Lambda(propertyAccess, pe);
MethodCallExpression orderByCallExpression = Expression.Call(
typeof(Queryable),
"OrderBy",
new[] { ModelType, propertyInfoOrderBy.PropertyType },
whereCallExpression,
Expression.Quote(orderByExp));
if (desc)
{
MethodCallExpression resultExp = Expression.Call(
typeof(Queryable),
"OrderByDescending",
new[] { ModelType, propertyInfoOrderBy.PropertyType },
orderByCallExpression,
Expression.Quote(orderByExp));
results = dbSet.Provider.CreateQuery<T>(resultExp);
}
else
{
results = dbSet.Provider.CreateQuery<T>(orderByCallExpression);
}
return results.ToList();
}
expressions :
body :
whereCallExpression :
orderByCallExpression :
error :
JN_News class :
public class JN_News
{
public int ID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string NewsLink { get; set; }
public DateTime PubDate { get; set; }
public string ImageLink { get; set; }
public bool IsDisplay { get; set; }
public Decimal? Rate { get; set; }
public int NewsCategories_ID { get; set; }
public virtual JN_NewsCategories JN_NewsCategories { get; set; }
}
JN_NewsCategories class :
public class JN_NewsCategories
{
public int ID { get; set; }
public string NewsCategoriesFa { get; set; }
public string NewsCategoriesEn { get; set; }
public bool IsGetNews { get; set; }
public virtual ICollection<JN_News> JN_News { get; set; }
public JN_NewsCategories()
{
JN_News = new Collection<JN_News>();
}
}
update :
When I've eliminated the following two statements. Working properly:
my all code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace NewsSiteApk.Data.DynamicSearchLibrary
{
public static class SearchUsingExpression<T> where T : class
{
public static IEnumerable<T> Search(IEnumerable<T> listOfT, string search, string orderBy, bool desc, int pageIndex, int pageSize)
{
listOfT = GetListOfData(listOfT, search, orderBy, desc).Skip((pageIndex - 1) * pageSize).Take(pageSize);
return listOfT;
}
public static int GetCount(IEnumerable<T> listOfT, string search)
{
listOfT = GetListOfData(listOfT, search, "id", true);
return listOfT.Count();
}
private static IEnumerable<T> GetListOfData(IEnumerable<T> listOfT, string search, string orderBy, bool desc)
{
var modelType = typeof(T);
ParameterExpression pe = Expression.Parameter(modelType, "d");
var expressions = new List<Expression>();
if (!string.IsNullOrEmpty(search))
{
expressions.AddRange(GetExpressions(modelType.Name, search, pe));
}
var connectToDatabase = ConnectToDatabase(listOfT.AsQueryable(), pe, expressions, orderBy, desc);
return connectToDatabase;
}
private static IEnumerable<T> ConnectToDatabase(IQueryable dbSet, ParameterExpression pe, IEnumerable<Expression> expressions,
string orderby, bool desc)
{
Expression body = null;
if (expressions.Any())
{
foreach (Expression expression in expressions)
{
body = ExpressionExtensions.JoinExpressions(body == null, Expression.Or, body, expression);
}
}
if (body == null)
{
Expression left = Expression.Property(pe, "ID");
Expression right = Expression.Constant(-1);
body = Expression.NotEqual(left, right);
}
IQueryable<T> results;
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new[] { dbSet.ElementType },
dbSet.Expression,
Expression.Lambda<Func<T, bool>>(body, new[] { pe }));
var propertyInfoOrderBy = GetPropertyInfo(orderby);
var propertyAccess = Expression.MakeMemberAccess(pe, propertyInfoOrderBy);
var orderByExp = Expression.Lambda(propertyAccess, pe);
var ModelType = typeof(T);
MethodCallExpression orderByCallExpression = Expression.Call(
typeof(Queryable),
"OrderBy",
new[] { ModelType, propertyInfoOrderBy.PropertyType },
whereCallExpression,
Expression.Quote(orderByExp));
if (desc)
{
MethodCallExpression resultExp = Expression.Call(
typeof(Queryable),
"OrderByDescending",
new[] { ModelType, propertyInfoOrderBy.PropertyType },
orderByCallExpression,
Expression.Quote(orderByExp));
results = dbSet.Provider.CreateQuery<T>(resultExp);
}
else
{
results = dbSet.Provider.CreateQuery<T>(orderByCallExpression);
}
return results.ToList();
}
private static IEnumerable<Expression> GetExpressions(string modelName, string search, ParameterExpression pe)
{
var expressions = new List<Expression>();
var fieldModels = GetFields(modelName);
foreach (FieldModel fieldModel in fieldModels)
{
IEnumerable<Expression> conditionsWithSubModel;
if (fieldModel.NameEn.Contains("[]"))
{
conditionsWithSubModel = GetConditionsWithSubModelList(search, fieldModel.NameEn, pe);
}
else if (fieldModel.NameEn.Contains("."))
{
conditionsWithSubModel = GetConditionsWithSubModel(search, fieldModel.NameEn);
}
else
{
conditionsWithSubModel = GetConditionsWithoutSubModel(search, pe, fieldModel.NameEn);
}
if (conditionsWithSubModel != null)
expressions.AddRange(conditionsWithSubModel);
}
return expressions;
}
private static IEnumerable<Expression> GetConditionsWithoutSubModel(string search, ParameterExpression pe,
string parametr)
{
var expressions = new List<Expression>();
foreach (var splitSeacrh in search.Split(' '))
{
Expression left = Expression.Property(pe, parametr);
Expression right = Expression.Constant(splitSeacrh);
MethodCallExpression conditionExpression = Expression.Call(left, typeof(string).GetMethod("Contains"),
right);
expressions.Add(conditionExpression);
}
return expressions;
}
private static IEnumerable<Expression> GetConditionsWithSubModel(string search,
string parameter)
{
// output.Where(d => d.JN_NewsCategories.NewsCategoriesEn.Contains(""));
var expressions = new List<Expression>();
var strings = parameter.Split('$');
var modelName = strings[0]; // Like : JN_News
var subModelName = strings[1].Split('.')[0];// Like : JN_NewsCategories
var subModelField = strings[1].Split('.')[1];// Like : NewsCategoriesEn
foreach (var splitSeacrh in search.Split(' '))
{
Type modelClass = GetModel(modelName);
Type submodelClass = GetModel(subModelName);
ParameterExpression peSubModel = Expression.Parameter(modelClass, "d");
Expression leftSubModel = Expression.Property(peSubModel, modelClass.GetProperty(subModelName));
Expression ex = Expression.Property(leftSubModel, submodelClass.GetProperty(subModelField));
Expression rightSubModel = Expression.Constant(splitSeacrh);
MethodCallExpression conditionExpressionSubModel = Expression.Call(ex,
typeof(string).GetMethod("Contains"), rightSubModel);
expressions.Add(conditionExpressionSubModel);
}
return expressions;
}
private static IEnumerable<Expression> GetConditionsWithSubModelList(string search, string parameter,
ParameterExpression peModel)
{
parameter = parameter.Replace("[]", string.Empty);
var expressions = new List<Expression>();
var subModelName = parameter.Split('.')[0];
var subModelField = parameter.Split('.')[1];
foreach (var splitSeacrh in search.Split(' '))
{
Type modelClass = GetModel(subModelName);
var subModelProperty = GetPropertyInfo(subModelName);
ParameterExpression peSubModel = Expression.Parameter(modelClass, "d");
Expression leftSubModel = Expression.Property(peSubModel, modelClass.GetProperty(subModelField));
Expression rightSubModel = Expression.Constant(splitSeacrh);
MethodCallExpression conditionExpressionSubModel = Expression.Call(leftSubModel,
typeof(string).GetMethod("Contains"), rightSubModel);
LambdaExpression anyLambdaSubModelForModel = Expression.Lambda(conditionExpressionSubModel, peSubModel);
MethodInfo anyMethodForModel = CreateAnyMethodGeneric(subModelProperty);
Expression lambedaSubModelForExpressionModel = Expression.Property(peModel, subModelProperty);
Expression expression = Expression.Call(anyMethodForModel, lambedaSubModelForExpressionModel,
anyLambdaSubModelForModel);
expressions.Add(expression);
}
return expressions;
}
private static Type GetModel(string name)
{
return (typeof(T).Assembly).GetTypes()
.First(d => string.Equals(d.Name, name, StringComparison.CurrentCultureIgnoreCase));
}
private static PropertyInfo GetPropertyInfo(string name)
{
return typeof(T).GetProperties().First(d => string.Equals(d.Name, name, StringComparison.CurrentCultureIgnoreCase));
}
private static MethodInfo CreateAnyMethodGeneric(PropertyInfo propYekiBeAkhar, string methodName = "Any")
{
return
typeof(Enumerable).GetMethods()
.Single(m => m.Name == methodName && m.GetParameters().Length == 2)
.MakeGenericMethod(propYekiBeAkhar.PropertyType.GenericTypeArguments[0]);
}
private static IEnumerable<FieldModel> GetFields(string modelName)
{
var fieldsFactory = new FieldsFactory();
var fieldModels = fieldsFactory.GetClause(modelName);
return fieldModels;
}
}
}
field factory class :
public class FieldsFactory
{
public List<FieldModel> GetClause(string modelName)
{
var type = typeof(FieldsFactory);
var methodInfos = type.GetMethod("Get" + modelName + "Fields", BindingFlags.NonPublic | BindingFlags.Instance);
var listOfFields = (List<FieldModel>)methodInfos.Invoke(this, null);
return listOfFields;
}
private List<FieldModel> GetJN_NewsCategoriesFields()
{
var fields = new List<FieldModel>
{
new FieldModel
{
NameEn = "NewsCategoriesFa",
},
new FieldModel
{
NameEn = "NewsCategoriesEn",
},
new FieldModel
{
NameEn = "JN_News[].Title",
},
new FieldModel
{
NameEn = "JN_News[].Description",
}
};
return fields;
}
private List<FieldModel> GetJN_NewsFields()
{
var fields = new List<FieldModel>
{
new FieldModel
{
NameEn = "Title",
},
new FieldModel
{
NameEn = "JN_News$JN_NewsCategories.NewsCategoriesFa",
},
new FieldModel
{
NameEn = "JN_News$JN_NewsCategories.NewsCategoriesEn",
}
};
return fields;
}
}
i fooooooooooound. I'm very happy.
Only , I replaced the following method :
private static IEnumerable<Expression> GetConditionsWithSubModel(string search, ParameterExpression pe,
string parameter)
{
// output.Where(d => d.JN_NewsCategories.NewsCategoriesEn.Contains(""));
var expressions = new List<Expression>();
var strings = parameter.Split('$');
var modelName = strings[0];
var subModelName = strings[1].Split('.')[0];
var subModelField = strings[1].Split('.')[1];
foreach (var splitSeacrh in search.Split(' '))
{
Type modelClass = GetModel(modelName);
Type submodelClass = GetModel(subModelName);
ParameterExpression peSubModel = Expression.Parameter(modelClass, "d");
Expression leftSubModel = Expression.Property(peSubModel, modelClass.GetProperty(subModelName));
Expression ex = Expression.Property(leftSubModel, submodelClass.GetProperty(subModelField));
Expression rightSubModel = Expression.Constant(splitSeacrh);
MethodCallExpression conditionExpressionSubModel = Expression.Call(ex,
typeof(string).GetMethod("Contains"), rightSubModel);
expressions.Add(conditionExpressionSubModel);
}
return expressions;
}
with following method :
private static IEnumerable<Expression> GetConditionsWithSubModel(string search, ParameterExpression pe,
string parameter)
{
// output.Where(d => d.JN_NewsCategories.NewsCategoriesEn.Contains(""));
var expressions = new List<Expression>();
var strings = parameter.Split('$');
var modelName = strings[0];
var subModelName = strings[1].Split('.')[0];
var subModelField = strings[1].Split('.')[1];
foreach (var splitSeacrh in search.Split(' '))
{
Type modelClass = GetModel(modelName);
Type submodelClass = GetModel(subModelName);
Expression leftSubModel = Expression.Property(pe, modelClass.GetProperty(subModelName));
Expression ex = Expression.Property(leftSubModel, submodelClass.GetProperty(subModelField));
Expression rightSubModel = Expression.Constant(splitSeacrh);
MethodCallExpression conditionExpressionSubModel = Expression.Call(ex,
typeof(string).GetMethod("Contains"), rightSubModel);
expressions.Add(conditionExpressionSubModel);
}
return expressions;
}
I have this two classes:
public class Contratos
{
//...
public int EntidadeFinanceiraId { get; set; }
[Column("Nome")]
public EntidadesFinanceiras entidadeFinanceira { get; set; }
//...
}
public class EntidadesFinanceiras
{
[Key]
public int ID { get; set; }
public string Nome { get; set; }
//...
}
and want to dinamically filter a List of Contratos based on Contratos.entidadeFinanceira.Nome. This is part of a Method that filters the list based on a property selected by the user.
public IQueryable<Models.Contratos> applyLambdaFilter(string val, string col, string oper, IQueryable<Models.Contratos> contratosList)
{
if (!string.IsNullOrWhiteSpace(val))
{
string typeName;
string columnName;
Type propType;
string[] propName = col.Split(new[] { '.' });
if (propName.Count() > 1)
{
typeName = "GAcordos.Models." + propName[0]; //entidadeFinanceira
columnName = propName[1]; //Nome
propType = Type.GetType("GAcordos.Models.Contratos").GetProperty(propName[0]).PropertyType.GetProperty(columnName).PropertyType; //String
}
else
{
typeName = "GAcordos.Models.Contratos";
columnName = propName[0]; //Other Contratos property
propType = Type.GetType(typeName).GetProperty(columnName).PropertyType;
}
if (propType != null)
{
var fixedItem = Comparators.getFixedItemWithType(val, propType);
var param = Expression.Parameter(typeof(GAcordos.Models.Contratos), "x");
var body = Expression.Equal(Expression.PropertyOrField(param, col.ToString()), fixedItem, false, Type.GetType("GAcordos.Helpers.Comparators").GetMethod(oper, new Type[] { propType, propType }));
var lambda = Expression.Lambda<Func<GAcordos.Models.Contratos, bool>>(body, param);
contratosList = contratosList.Where(lambda.Compile()).AsQueryable();
}
}
return contratosList;
}
When the Method executes it throws an exception 'entidadeFinanceira.Nome' is not a member of type 'GAcordos.Models.Contratos' on the line
var body = Expression.Equal(Expression.PropertyOrField(param, col.ToString()), fixedItem, false, Type.GetType("GAcordos.Helpers.Comparators").GetMethod(oper, new Type[] { propType, propType }));
But if I write the expression directly:
contratosList = contratosList.Where(x => x.entidadeFinanceira.Nome == val);
it works fine.
So, how can I build the lambda expression x => x.property.property == constVal?
Simply, you need two uses of PropertyOrField.
Constructed manually, x => x.Foo.Bar == constVal is:
var param = Expression.Parameter(typeof(ObjectType), "x");
var lambda = Expression.Lambda<Func<ObjectType, bool>>(
Expression.Equal(
Expression.PropertyOrField(
Expression.PropertyOrField(param, "Foo"), "Bar"
), Expression.Constant(constVal, constValType)
), param);
(note that it is important to include constValType in case constVal is null; this avoids a lot of unexpected problems)
Seems that when calling
Expression.PropertyOrField(param, col.ToString())
the variable col contains "entidadeFinanceira.Nome". You may reuse all the splitting of col you did above and do something like:
Expression property = param;
foreach(var pName in propName) {
property = Expression.PropertyOrField(property, pName);
}
Now the expression property should be correct and you can use it to build the body expression:
var body = Expression.Equal(
property,
fixedItem,
false,
Type
.GetType("GAcordos.Helpers.Comparators")
.GetMethod(oper, new Type[] { propType, propType })
);