Using dynamic linq extensions with jqgrid on one to many relationships - c#

Im not sure if the title of the question is correct if not let me know and i will change it. Okay so firstly im using jgrid in a asp.net application. I have a helper that sets up the page size, rows and does the sorting. The issue is i can only sort on parameters for the current table it does not work when the parameter name is from a separate table connected through a relationship.
The linq extension is for orderBy and thenBy, they both take a string parameter and sort order parameter. I have tried setting the string parameter as "WaterSample.Name", watersample being another table from a relationship and name a parameter in that table but unfortunately i cannot do it this way in c# since i am using an anonymous type. So my question is can i make the line extensions search through the relationships to find match the property name. Sorry if this is a bit confusing im still fairly new to linq.
i have included the class below that contains the extensions.
namespace Helpers
{
[ModelBinder(typeof(GridModelBinder))]
[Serializable]
public class JqGridSettings
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public string SortColumn { get; set; }
public string SortOrder { get; set; }
public string SortColumn2 { get; set; }
public string SortOrder2 { get; set; }
public string SortColumn3 { get; set; }
public string SortOrder3 { get; set; }
public JqGridSettings()
{
}
/// <summary>
/// Initializes a new instance of the GridSettings class.
/// </summary>
/// <param name="stringData">The string that represents the a GridSettings object.</param>
public JqGridSettings(string stringData)
{
var tempArray = stringData.Split(new[] { "#;" }, StringSplitOptions.None);
PageSize = int.Parse(tempArray[0]);
PageIndex = int.Parse(tempArray[1]);
SortColumn = tempArray[2];
SortOrder = tempArray[3];
}
/// <summary>
/// Build a string used to cache the current GridSettings object.
/// </summary>
/// <returns>A string that represents the current GridSettings object.</returns>
public override string ToString()
{
return string.Format("{0}#;{1}#;{2}#;{3}", PageSize, PageIndex, SortColumn,SortOrder);
}
public IQueryable<T> LoadGridData<T>(IQueryable<T> dataSource, out int count)
{
var query = dataSource;
//
// Sorting and Paging by using the current grid settings.
//
query = query.OrderBy<T>(SortColumn, SortOrder);
if (String.IsNullOrEmpty(SortColumn2) == false)
{
query = query.ThenBy<T>(SortColumn2, SortOrder2);
}
if (String.IsNullOrEmpty(SortColumn3) == false)
{
query = query.ThenBy<T>(SortColumn3, SortOrder3);
}
count = query.Count();
//
if (PageIndex < 1)
PageIndex = 1;
//
var data = query.Skip((PageIndex - 1) * PageSize).Take(PageSize);
return data;
}
}
public static class LinqExtensions
{
public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string sortColumn, string direction)
{
var methodName = string.Format("OrderBy{0}", direction.ToLower() == "asc" ? "" : "descending");
var parameter = Expression.Parameter(query.ElementType, "p");
var memberAccess = sortColumn.Split('.').Aggregate<string, MemberExpression>(null, (current, property) => Expression.Property(current ?? (parameter as Expression), property));
var orderByLambda = Expression.Lambda(memberAccess, parameter);
var result = Expression.Call(typeof(Queryable),methodName,new[]{query.ElementType, memberAccess.Type},query.Expression,Expression.Quote(orderByLambda));
return query.Provider.CreateQuery<T>(result);
}
public static IQueryable<T> ThenBy<T>(this IQueryable<T> query, string sortColumn, string direction)
{
var methodName = string.Format("ThenBy{0}", direction.ToLower() == "asc" ? "" : "descending");
var parameter = Expression.Parameter(query.ElementType, "p");
var memberAccess = sortColumn.Split('.').Aggregate<string, MemberExpression>(null, (current, property) => Expression.Property(current ?? (parameter as Expression), property));
var orderByLambda = Expression.Lambda(memberAccess, parameter);
var result = Expression.Call(typeof(Queryable), methodName, new[] { query.ElementType, memberAccess.Type }, query.Expression, Expression.Quote(orderByLambda));
return query.Provider.CreateQuery<T>(result);
}
}
public class GridModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
try
{
var request = controllerContext.HttpContext.Request;
return new JqGridSettings
{
PageIndex = int.Parse(request["page"] ?? "1"),
PageSize = int.Parse(request["rows"] ?? "50"),
SortColumn = request["sidx"] ?? "",
SortOrder = request["sord"] ?? "asc",
};
}
catch
{
//
// For unexpected errors use the default settings!
//
return null;
}
}
}
}
Thank you for any help or suggestions.

Related

Linq expression orderBy on child collections

I've got this class with a collection of revisions:
public class Client : BaseEntity
{
public virtual ICollection<Draw> Draws { get; set; }
public virtual ICollection<ClientRevision> ClientRevisions { get; set; }
}
public class ClientRevision : BaseEntity
{
public Guid ClientId { get; set; }
public Client Client { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double? Increase { get; set; }
public string RevisionCode { get; set; }
public string RevisionNote { get; set; }
}
On controller, I'd like to retrieve the list of clients with the last revision available. I've got a base repository with the following get method:
public async Task<IEnumerable<T>> GetAsync(
Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
IEnumerable<Expression<Func<T, object>>> includeProperties = null,
int? pageIndex = null,
int? itemsPerPage = null,
bool ignoreQueryFilter = true,
bool ignoreTracking = true)
{
var query = DataContext.GetData<T>(ignoreTracking);
if (filter != null)
{
query = query.Where(filter);
}
if (orderBy != null)
{
query = orderBy(query);
}
if (ignoreQueryFilter)
{
query = query.IgnoreQueryFilters();
}
query = ApplyIncludePropertiesIfNeeded(includeProperties, query);
if (pageIndex.HasValue && itemsPerPage.HasValue)
{
query = query
Skip(pageIndex.Value * itemsPerPage.Value)
Take(itemsPerPage.Value + 1);
}
return await query.ToListAsync();
}
I'd like to order by the list by the Name of the client; the name is in the Client Revision. I try to implement the following filter and orderby, but I cannot create a correct linq expression for retrive the Name of the Client.
public async Task<ActionResult<ClientResponse>> GetClientListAsync()
{
var result = new List<ClientResponse>();
List<Expression<Func<Client, object>>> includes = new() { i => i.Draws };
var sortOn = "ClientRevisions.Name";
var param = Expression.Parameter(typeof(Client), "client");
var parts = sortOn.Split('.');
Expression parent = param;
foreach (var part in parts)
{
parent = Expression.Property(parent, part);
}
var sortExpression = Expression.Lambda<Func<Client, object>>(parent, param);
Func<IQueryable<Client>, IOrderedQueryable<Client>> order = o => o.OrderBy(sortExpression);
}
The error is the following:
Instance property 'Name' is not defined for type 'System.Collections.Generic.ICollection`1[ClientRevision]' (Parameter 'propertyName')
Because is a list. How to retrieve this data from a list? Thanks

Enum Extension method call by Enum Name?

I have following code which is working fine . but i need to use extension method with its Enum type.
public class Dropdown
{
public Dropdown() { }
public Dropdown(int id, string name)
{
Id = id;
Name = name;
}
public Dropdown(string name)
{
Name = name;
}
public int Id { get; set; }
public string StringId { get; set; }
public string Name { get; set; }
}
public enum AccidentTypeEnum
{
[Display(Name = "Minor")]
Minor = 0,
[Display(Name = "Major")]
Major = 1,
[Display(Name = "Severe")]
Severe = 2
}
Extension Method
public static class EnumExtensions
{
public static List<Dropdown> ConvertToDropdown(this Enum mEnum)
{
var dropDownlist = new List<Dropdown>();
var enumType = mEnum.GetType();
var enumValuies = Enum.GetValues(enumType);
foreach (var singleValue in enumValuies)
{
dropDownlist.Add(new Dropdown { Id = (int)singleValue, Name = singleValue.ToString() });
}
return dropDownlist;
}
}
Question:
Above code is working fine if i call extension method using below lines
var TestAcident = AccidentTypeEnum.Major;
var resultDropdown = TestAcident.ConvertToDropdown();
But how can i call it just like below lines
var resultDropdown = AccidentTypeEnum.ConvertToDropdown(); //<-- i need to use like this. but it not working
You can do that
List<AccidentTypeEnum> types = Utils<AccidentTypeEnum>.ConvertToDropdown();
this way:
/// <summary>Pseudo extension class for enumerations</summary>
/// <typeparam name="TEnum">Enumeration type</typeparam>
public class Utils<TEnum> where TEnum : struct, IConvertible
{
public static List<Dropdown> ConvertToDropdown()
{
var enumType = typeof(TEnum);
return enumType.IsEnum
? enumType.GetEnumValues()
.OfType<TEnum>()
.Select(e => new Dropdown
{
Id = Convert.ToInt32(Enum.Parse(enumType, e.ToString()) as Enum),
Name = GetDisplay(e)
})
.ToList()
: throw new ArgumentException($"{enumType.Name} is not enum");
}
private static string GetDisplay<T>(T value)
{
var enumValueText = value.ToString();
var displayAttribute = value
.GetType()
.GetField(enumValueText)
.GetCustomAttributes(typeof(DisplayAttribute), false)
.OfType<DisplayAttribute>()
.FirstOrDefault();
return displayAttribute == null ? enumValueText : displayAttribute.Description;
}
}

DbSortClause expressions must have a type that is order comparable. Parameter name: key

when i use from OrderBy i get this error DbSortClause expressions must have a type that is order comparable. Parameter name: key.
i do not know how to change this code
var resAsc = this.Context.Set<TEntity>().AsNoTracking().Where(predicate: predicate).OrderBy(s => sortItem.SortItems.Select(w => w.SortText).ToList()).Skip(page * size).Take(page).ToList().AsQueryable();
how can i resolve it ?
Edit : I want send parameters to this method for example
string test = "Id";
SortOption objsort = new SortOption();
objsort.SortItems = new List<SortItem>();
objsort.SortItems.Add(new SortItem { SortText = "Id" });
objsort.SortOrderType = EnumTypes.SortOrder.Ascending;
var res = ApplicationService.SearchPage(w => w.Id > 2, objsort, 1, 3);
and now i get these parameters here
public Paginated<TEntity> SearchPage(Expression<Func<TEntity, bool>> predicate, SortOption sortItem, int page, int size)
{
Paginated<TEntity> objPage = new Paginated<TEntity>();
if (sortItem.SortOrderType == EnumTypes.SortOrder.Ascending)
{
var resAsc = this.Context.Set<TEntity>().AsNoTracking().Where(predicate: predicate).OrderBy(s => sortItem.SortItems.Select(w => w.SortText).ToList()).Skip(page * size).Take(page).ToList().AsQueryable();
objPage.Data = resAsc;
objPage.TotalCount = this.Context.Set<TEntity>().Count();
return objPage;
}
var resDesc = this.Context.Set<TEntity>().AsNoTracking().Where(predicate: predicate).OrderByDescending(s => sortItem.SortItems.Select(w => w.SortText)).Skip(page * size).Take(page).ToList().AsQueryable();
objPage.Data = resDesc;
objPage.TotalCount = this.Context.Set<TEntity>().Count();
return objPage;
}
Actually, i want get this Id in the here
var resAsc = this.Context.Set<TEntity>().AsNoTracking().Where(predicate: predicate).OrderBy(s => s.Id).Skip(page * size).Take(page).ToList().AsQueryable();
You could use the following code or take inspiration from it. It gives you an extension method IEnumerable<TEntity> called Prepare. This method will select the items which match an predicate then it will order the entities and finnaly paginate it.
You can give as many ColumnOrderConfiguration objects as you want. It will use OrderBy and ThenBy to create the correct result.
Just keep in mind that you will have to use Expression<Func<,>> instead of Func<,> and IDbSet instead of IEnumerable when you work with an database.
public class ColumnOrderConfiguration<TEntity>
{
public Func<TEntity, object> ValueSelector { get; set; } = entity => null;
public SortOrder SortOrder { get; set; } = SortOrder.Ascending;
}
public static class CollectionPreparationExtensions
{
public static IEnumerable<TEntity> Prepare<TEntity>(this IEnumerable<TEntity> entities, Func<TEntity, bool> predicate, IEnumerable<ColumnOrderConfiguration<TEntity>> orderConfiguration, int pageIndex, int pageSize)
=> entities.Where(predicate).OrderBy(orderConfiguration).Skip(pageIndex * pageSize).Take(pageSize);
private static IEnumerable<TEntity> OrderBy<TEntity>(this IEnumerable<TEntity> entities, IEnumerable<ColumnOrderConfiguration<TEntity>> orderConfiguration)
{
var configurations = orderConfiguration.ToArray();
if (!configurations.Any())
return entities;
var firstOrderConfiguration = configurations.First();
var orderedEntities = entities.OrderBy(firstOrderConfiguration.ValueSelector, firstOrderConfiguration.SortOrder);
for (var i = 1; i < configurations.Length; i++)
{
orderedEntities = orderedEntities.ThenBy(configurations[i].ValueSelector, configurations[i].SortOrder);
}
return orderedEntities;
}
private static IOrderedEnumerable<TEntity> ThenBy<TEntity>(this IOrderedEnumerable<TEntity> entities, Func<TEntity, object> valueSelector, SortOrder sortOrder)
{
if (sortOrder == SortOrder.Descending)
return entities.ThenByDescending(valueSelector);
return entities.ThenBy(valueSelector);
}
private static IOrderedEnumerable<TEntity> OrderBy<TEntity>(this IEnumerable<TEntity> entities, Func<TEntity, object> valueSelector, SortOrder sortOrder)
{
if (sortOrder == SortOrder.Descending)
return entities.OrderByDescending(valueSelector);
return entities.OrderBy(valueSelector);
}
}
And this is how you use it.
public class MyTestEntity
{
public bool IsTrue { get; set; }
public string OrderText { get; set; }
public int ThenOrderBy { get; set; }
}
var entities = new List<MyTestEntity>(new []
{
new MyTestEntity { IsTrue = true, OrderText = "1234", ThenOrderBy = 4321 },
new MyTestEntity { IsTrue = true, OrderText = "000001", ThenOrderBy = 000001 },
new MyTestEntity { IsTrue = false }
});
var searchPredicate = new Func<MyTestEntity, bool>(entity => entity.IsTrue);
var orderConfig = new List<ColumnOrderConfiguration<MyTestEntity>>(new []
{
// first order by `OrderText` ascending
new ColumnOrderConfiguration<MyTestEntity>
{
ValueSelector = entity => entity.OrderText,
SortOrder = SortOrder.Ascending
},
// then order by `ThenOrderBy` descending
new ColumnOrderConfiguration<MyTestEntity>
{
ValueSelector = entity => entity.ThenOrderBy,
SortOrder = SortOrder.Descending
}
});
var pageIndex = 0;
var pageSize = 20;
var result = entities.Prepare(searchPredicate, orderConfig, pageIndex, pageSize);

Send lambda expression from client to server

I have a method that helps to dynamically build a query on a client:
public virtual IList<TEntity> Fill(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
includeProperties = includeProperties ?? "";
var qctx = new TQueryContext
{
QueryType = filter == null ? CommonQueryType.FillAll : CommonQueryType.FillWhere,
Filter = filter,
OrderBy = orderBy
};
qctx.Includes.AddRange(includeProperties.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
_detachedServerRepo.Read(qctx);
return qctx.Entities;
}
I want to send qctx to a server repo which might be on another machine. Since a TQueryContext will be typed from QueryContextBase defined in part as below I can't serialize it.
public class QueryContextBase<TEntity, TKey>
where TEntity : StateTrackedObject
where TKey : IEquatable<TKey>
{
public TKey ID { get; set; }
public string Alf { get; set; }
public List<TEntity> Entities { get; set; }
public List<string> Includes { get; set; }
public Expression<Func<TEntity, bool>> Filter { get; set; }
public Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> OrderBy { get; set; }
}
How can I create similar properties to Filter and OrderBy so I can serialize them and then build up the query in the server repo as below:
protected override void FillWhere(TQueryContext qctx)
{
qctx.Entities.AddRange(this.Fill(qctx.Filter, qctx.OrderBy,
qctx.GetIncludesAsString()));
}
protected override void FillAll(TQueryContext qctx)
{
qctx.Entities.AddRange(this.Fill(null, qctx.OrderBy, qctx.GetIncludesAsString()));
}
public virtual IEnumerable<TEntity> Fill(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
includeProperties = includeProperties ?? "";
try
{
IQueryable<TEntity> querySet = DbSet;
if (filter != null)
{
querySet = querySet.Where(filter);
}
foreach (var includeProperty in includeProperties.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
querySet = querySet.Include(includeProperty.Trim());
}
return (orderBy == null) ? querySet.ToList() : orderBy(querySet).ToList();
}
catch (Exception ex)
{
return ex.ThrowDalException<IEnumerable<TEntity>>(OperationType.Read, ex.Message, ex);
}
}
You're right not to want to reinvent the wheel. Check out Serialize.Linq.
You could solve your problem with this library as below:
In your client repo:
public virtual IList<TEntity> Fill(Expression<Func<TEntity, bool>> filter = null,
Expression<Func<IQueryable<TEntity>,
IOrderedQueryable<TEntity>>> orderBy = null,
string includeProperties = "") {
includeProperties = includeProperties ?? "";
try
{
var qctx = new TQueryContext
{
QueryType = filter == null ? CommonQueryType.FillAll : CommonQueryType.FillWhere,
FilterNode = filter == null ? null : filter.ToExpressionNode(),
OrderByNode = orderBy == null ? null : orderBy.ToExpressionNode()
};
And then in your QueryContext just add the extra property and convert:
public ExpressionNode FilterNode { get; set; }
public Expression<Func<TEntity, bool>> Filter
{
get {
return FilterNode == null ? null : FilterNode.ToBooleanExpression<TEntity>();
}
}
public ExpressionNode OrderByNode { get; set; }
public Expression<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>> OrderBy
{
get {
return OrderByNode == null ? null : OrderByNode.ToExpression<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>>();
}
}
It is not possible to serialize a method... any method (lambda, delegate, ...), thus it is not possible to send a method from client to server.
Best you can do is to send a filter to your service, construct the query there and return the result to the client. That is the usual way.
So in your case instead of passing a Func which filters the data pass the values which the filter uses.
To elaborate that, consider this example of server-side method:
DataType[] GetData(Filter filter, Ordering ordering)
{
var data = GetDataQuerySomeHow(); //for example in EF Context.Table
//filter data according to the filter
if (!string.IsNullOrEmpty(filter.FulltextProperty))
{
data = data.Where(a => a.StringProperty.Contains(filter.FulltextProperty));
}
//do similar thing for ordering
return data.ToArray();
}
Filter and ordering:
public class Filter
{
public string FulltextProperty { get; set; }
//some other filtering properties
}
public class Ordering
{
public string ColumnName { get; set; }
public bool Ascending { get; set; }
}
Client
Filter filter = new Filter()
{
//fill-in whatever you need
};
Ordering ordering = new Ordering(); //also fill in
var data = GetData(filter, ordering);
//display data somewhere

Linq IQueryable Generic Filter

I am looking for a generic Filter for the searchText in the query of any Column/Field mapping
public static IQueryable<T> Filter<T>(this IQueryable<T> source, string searchTerm)
{
var propNames = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(e=>e.PropertyType == typeof(String)).Select(x => x.Name).ToArray();
//I am getting the property names but How can I create Expression for
source.Where(Expression)
}
Here I am giving you an example scenario
Now From my HTML5 Table in Asp.net MVC4 , I have provided a Search box to filter results of entered text , which can match any of the below columns/ Menu class Property values , and I want to do this search in Server side , how can I implement it.
EF Model Class
public partial class Menu
{
public int Id { get; set; }
public string MenuText { get; set; }
public string ActionName { get; set; }
public string ControllerName { get; set; }
public string Icon { get; set; }
public string ToolTip { get; set; }
public int RoleId { get; set; }
public virtual Role Role { get; set; }
}
You can use Expressions:
private static Expression<Func<T, bool>> GetColumnEquality<T>(string property, string term)
{
var obj = Expression.Parameter(typeof(T), "obj");
var objProperty = Expression.PropertyOrField(obj, property);
var objEquality = Expression.Equal(objProperty, Expression.Constant(term));
var lambda = Expression.Lambda<Func<T, bool>>(objEquality, obj);
return lambda;
}
public static IQueryable<T> Filter<T>(IQueryable<T> source, string searchTerm)
{
var propNames = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(e => e.PropertyType == typeof(string))
.Select(x => x.Name).ToList();
var predicate = PredicateBuilder.False<T>();
foreach(var name in propNames)
{
predicate = predicate.Or(GetColumnEquality<T>(name, searchTerm));
}
return source.Where(predicate);
}
Combined with the name PredicateBuilder from C# in a NutShell. Which is also a part of LinqKit.
Example:
public class Foo
{
public string Bar { get; set; }
public string Qux { get; set; }
}
Filter<Foo>(Enumerable.Empty<Foo>().AsQueryable(), "Hello");
// Expression Generated by Predicate Builder
// f => ((False OrElse Invoke(obj => (obj.Bar == "Hello"), f)) OrElse Invoke(obj => (obj.Qux == "Hello"), f))
void Main()
{
// creates a clause like
// select * from Menu where MenuText like '%ASD%' or ActionName like '%ASD%' or....
var items = Menu.Filter("ASD").ToList();
}
// Define other methods and classes here
public static class QueryExtensions
{
public static IQueryable<T> Filter<T>(this IQueryable<T> query, string search)
{
var properties = typeof(T).GetProperties().Where(p =>
/*p.GetCustomAttributes(typeof(System.Data.Objects.DataClasses.EdmScalarPropertyAttribute),true).Any() && */
p.PropertyType == typeof(String));
var predicate = PredicateBuilder.False<T>();
foreach (var property in properties )
{
predicate = predicate.Or(CreateLike<T>(property,search));
}
return query.AsExpandable().Where(predicate);
}
private static Expression<Func<T,bool>> CreateLike<T>( PropertyInfo prop, string value)
{
var parameter = Expression.Parameter(typeof(T), "f");
var propertyAccess = Expression.MakeMemberAccess(parameter, prop);
var like = Expression.Call(propertyAccess, "Contains", null, Expression.Constant(value,typeof(string)));
return Expression.Lambda<Func<T, bool>>(like, parameter);
}
}
You need to add reference to LinqKit to use PredicateBuilder and AsExpandable method
otherwise won't work with EF, only with Linq to SQL
If you want Col1 like '%ASD%' AND Col2 like '%ASD%' et, change PredicateBuilder.False to PredicateBuilder.True and predicate.Or to predicate.And
Also you need to find a way to distinguish mapped properties by your own custom properties (defined in partial classes for example)

Categories