Dynamic query builder for .Net realm - c#

I am using Realm database for storing data locally on my device. In the application a user can see a list of books and apply Filters/Sorting on them. A book is a Pojo object which has a Title, Author(string), Status(Enum) and Publish-Date(time stamp). The user of the application can filter books based on those four fields, there are more fields on the object but for simplicity I am only using 4 at the moment.
I am trying to create a dynamic query builder where all the selected filters will be applied on the query.
What I have seen in the documentation is that we can query realm like this
var allBooks = realm.All<Book>().Where(book =>
book.Title== "Star wars" ||
book.Status== "Sold");
what I want is to construct the query on my own by passing the list of filters int the method. I was thinking of creating a wrapper class with Key/Value pair and assigning the name of the filter and the value. Then I could pass a list of those to a method. My question is how construct the query builder from a list of key/value where the key is the object field like "Title", "Status" and the value is "Star wars", "Sold".
Any help will be greatly appreciate.

You can create a dynamic predicate with Linq Expressions:
public static Expression<Func<T, bool>> CreatePredicate<T>(KeyValuePair<string, string>[] filters)
{
var type = typeof(T);
var parameter = Expression.Parameter(type, "t");
if (filters.Length == 0) // no filtering
return Expression.Lambda<Func<T, bool>>(Expression.Constant(true), parameter);
Expression body = Expression.Constant(false);
foreach (var filter in filters)
{
var member = Expression.PropertyOrField(parameter, filter.Key);
var value = Expression.Constant(filter.Value);
body = Expression.OrElse(body, Expression.Equal(member, value));
}
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
Now if All<Book>() returns an IQueryable<Book> use it like:
var predicate = CreatePredicate<Book>(new[]
{
new KeyValuePair<string, string>("Title", "Star wars"),
new KeyValuePair<string, string>("Status", "Sold"),
});
var allBooks = realm.All<Book>().Where(predicate);
In case if All<Book>() returns IEnumerable<Book> you still able to use it, just add call to Compile:
var allBooks = realm.All<Book>().Where(predicate.Compile());

Related

In LinqToEntities, How to pass dynamic column name to DbFunctions.Like

I have an IQueryable<T> from my DbSet in Entity Framework. I am provided a "Fuzzy Search String", named searchText, like so:
public List<T> Search<T>(string searchText)
{
using (var context = ...)
{
var baseQuery = context.Set<T>().AsQueryable();
baseQuery = baseQuery.Where(x =>
DbFunctions.Like(x.PropertyName, searchText)
|| DbFunctions.Like(x.PropertyTwo, searchText)
|| DbFunctions.Like(x.PropertyThree, searchText)
|| DbFunctio..... etc
);
return baseQuery.ToList();
}
}
But given the generic nature, I don't know what properties there are on the type. I can provide an abstract method to somebody implementing this which allows them to give me a List of Properties (or even PropertyInfo or whatever else, I can figure that out). But I don't know how to dynamically create the expression. This is what I have so far:
var baseQuery = context.Set<T>().AsQueryable();
var expression = baseQuery.Expression;
var colName = "colName"; // Or names, I can iterate.
var parameter = Expression.Parameter(typeof(T), "x");
var selector = Expression.PropertyOrField(parameter, colName);
expression = Expression.Call(typeof(DbFunctions), nameof(DbFunctions.Like),
new Type[] { baseQuery.ElementType, selector.Type },
expression, Expression.Quote(Expression.Lambda(selector, parameter)));
The problem here is... well, it doesn't work to begin with. But mainly that I'm not using the searchText anywhere in it, and don't know how to plug it in. I THINK I'm close... but have spent an inordinate amount of time on it.
Hopefully I'm getting your query logic right: if you want to build a set of LIKE conditions based on known type and list of column names, you could try something like this:
static private MethodInfo dbLikeMethod = typeof(DbFunctions).GetMethod(nameof(DbFunctions.Like), BindingFlags.Public | BindingFlags.Static, null, new Type[] {typeof(string), typeof(string)}, null); // I am targeting DbFunctions.Like(string, string). You might want another overload (or even mix them up depending on your inputs)
public List<T> Search<T>(string searchText) where T: class
{
using (var context = new ...)
{
var baseQuery = context.Set<T>().AsQueryable().Where(CreateExpression<T>(searchText));// you could probably find a more elegant way of plugging it into your query
return baseQuery.ToList();
}
}
Expression<Func<T, bool>> CreateExpression<T>(string searchText) where T : class
{
var cols = new List<string> {
"PropertyName",
"PropertyTwo" // i understand you've got a way to figure out which strings you need here
};
var parameter = Expression.Parameter(typeof(T), "x");
var dbLikeCalls = cols.Select(colName => Expression.Call(dbLikeMethod, Expression.PropertyOrField(parameter, colName), Expression.Constant(searchText))); // for convenience, generate list of DbFunctions.Like(x.<Property>, searchText) expressions here
var aggregatedCalls = dbLikeCalls.Skip(1).Aggregate((Expression)dbLikeCalls.First(), (accumulate, call) => Expression.OrElse(accumulate, call)); // aggregate the list using || operators: use first item as a seed and keep adding onto it
return Expression.Lambda<Func<T, bool>>(aggregatedCalls, parameter);
}

Dynamically build select list from linq to entities query

I'm looking for a way to dynamically create a select list from a iQueryable object.
Concrete example, i want to do something like the following:
public void CreateSelectList(IQueryable(of EntityModel.Core.User entities), string[] columns)
{
foreach(var columnID in columns)
{
switch(columnID)
{
case "Type":
SelectList.add(e => e.UserType);
break;
case "Name":
SelectList.add(e => e.Name);
break;
etc....
}
}
var selectResult = (from u in entities select objSelectList);
}
So all properties are known, i however don't know beforehand what properties are to be selected. That will be passed via the columns parameter.
I know i'm going to run into issues with the type of the selectResult type, because when the select list is dynamic, the compiler doesn't know what the properties of the anonymous type needs to be.
If the above is not possible: The scenario I need it for is the following:
I'm trying to create a class that can be implemented to display a paged/filtered list of data. This data can be anything (depends on the implementations).The linq used is linq to entities. So they are directly linked to sql data. Now i want to only select the columns of the entities that i am actually showing in the list. Therefore i want the select to be dynamic. My entity might have a hundred properties, but if only 3 of them are shown in the list, i don't want to generate a query that selects the data of all 100 columns and then only uses 3 of them. If there is a different approach that I haven't thought of, I'm open to ideas
Edit:
Some clarifications on the contraints:
- The query needs to work with linq to entities (see question subject)
- an entity might contain 100 columns, so selecting ALL columns and then only reading the ones i need is not an option.
- The end user decides what columns to show, so the columns to select are determined at run time
- i need to create a SINGLE select, having multiple select statements means having multiple queries on the database, which i don't want
Dynamic select expression to a compile time known type can easily be build using Expression.MemberInit method with MemberBindings created using the Expression.Bind method.
Here is a custom extension method that does that:
public static class QueryableExtensions
{
public static IQueryable<TResult> Select<TResult>(this IQueryable source, string[] columns)
{
var sourceType = source.ElementType;
var resultType = typeof(TResult);
var parameter = Expression.Parameter(sourceType, "e");
var bindings = columns.Select(column => Expression.Bind(
resultType.GetProperty(column), Expression.PropertyOrField(parameter, column)));
var body = Expression.MemberInit(Expression.New(resultType), bindings);
var selector = Expression.Lambda(body, parameter);
return source.Provider.CreateQuery<TResult>(
Expression.Call(typeof(Queryable), "Select", new Type[] { sourceType, resultType },
source.Expression, Expression.Quote(selector)));
}
}
The only problem is what is the TResult type. In EF Core you can pass the entity type (like EntityModel.Core.User in your example) and it will work. In EF 6 and earlier, you need a separate non entity type because otherwise you'll get NotSupportedException - The entity or complex type cannot be constructed in a LINQ to Entities query.
UPDATE: If you want a to get rid of the string columns, I can suggest you replacing the extension method with the following class:
public class SelectList<TSource>
{
private List<MemberInfo> members = new List<MemberInfo>();
public SelectList<TSource> Add<TValue>(Expression<Func<TSource, TValue>> selector)
{
var member = ((MemberExpression)selector.Body).Member;
members.Add(member);
return this;
}
public IQueryable<TResult> Select<TResult>(IQueryable<TSource> source)
{
var sourceType = typeof(TSource);
var resultType = typeof(TResult);
var parameter = Expression.Parameter(sourceType, "e");
var bindings = members.Select(member => Expression.Bind(
resultType.GetProperty(member.Name), Expression.MakeMemberAccess(parameter, member)));
var body = Expression.MemberInit(Expression.New(resultType), bindings);
var selector = Expression.Lambda<Func<TSource, TResult>>(body, parameter);
return source.Select(selector);
}
}
with sample usage:
var selectList = new SelectList<EntityModel.Core.User>();
selectList.Add(e => e.UserType);
selectList.Add(e => e.Name);
var selectResult = selectList.Select<UserDto>(entities);
What you are going for is possible, but it's not simple. You can dynamically build EF queries using the methods and classes in the System.Linq.Expressions namespace.
See this question for a good example of how you can dynamically build your Select expression.
I believe this is what you need:
var entities = new List<User>();
entities.Add(new User { Name = "First", Type = "TypeA" });
entities.Add(new User { Name = "Second", Type = "TypeB" });
string[] columns = { "Name", "Type" };
var selectResult = new List<string>();
foreach (var columnID in columns)
{
selectResult.AddRange(entities.Select(e => e.GetType().GetProperty(columnID).GetValue(e, null).ToString()));
}
foreach (var result in selectResult)
{
Console.WriteLine(result);
}
This code outputs:
First
Second
TypeA
TypeB
UPDATE (according to comments)
// initialize alist of entities (User)
var entities = new List<User>();
entities.Add(new User { Name = "First", Type = "TypeA", SomeOtherField="abc" });
entities.Add(new User { Name = "Second", Type = "TypeB", SomeOtherField = "xyz" });
// set the wanted fields
string[] columns = { "Name", "Type" };
// create a set of properties of the User class by the set of wanted fields
var properties = typeof(User).GetProperties()
.Where(p => columns.Contains(p.Name))
.ToList();
// Get it with a single select (by use of the Dynamic object)
var selectResult = entities.Select(e =>
{
dynamic x = new ExpandoObject();
var temp = x as IDictionary<string, Object>;
foreach (var property in properties)
temp.Add(property.Name, property.GetValue(e));
return x;
});
// itterate the results
foreach (var result in selectResult)
{
Console.WriteLine(result.Name);
Console.WriteLine(result.Type);
}
This code outputs:
First
TypeA
Second
TypeB

How to pass a user-input string as a field name to access a field within an object?

I'm doing a little bit of search from a database using linq. I have multiple column names like country, name, phone number...
Now I've created a dropdownlist and pass the user selected data as a parameter "searchedField" to my controller method. Now if I take the input of a "country", I expect the code to be
entries = entries.Where(s => s.country.Contains(searchString));
If user selected "name"
entries = entries.Where(s => s.name.Contains(searchString));
Excuse me for this rather unreasonable example, since I can always just copy lines and make cases, but I wonder if there is a way to utilize things like reflection to convert string to "code" to access a field?
String searchedField = "name"
...
entries = entries.Where(s => s.searchedField.Contains(searchString));
This is my first question here, thanks!
You can use Dynamic Linq.
entries = entries
.Where(
string.Format(
"{0} = '{1}'",
searchedField,
searchString
)
);
Note: depending on the type of field you'll need to add quotes, or not.
You can do a reflection lookup (note: I've omitted error checking):
string GetPropertyAsString(object obj, string propertyName)
{
var propertyInfo - obj.GetType().GetProperty(propertyName);
return propertyInfo.GetValue(obj).ToString();
}
and then say
entries = entries.Where(s => GetPropertyAsString(s, searchedField).Contains(searchString));
You can build an expression tree for this (which will work with Linq to entities).
public static class QueryableExtensions {
public static IQueryable<T> Filter<T>(this IQueryable<T> queryable, string propertyName, string searchValue)
{
var type = typeof (T);
//this will be the left part of the lambda
var parameter = Expression.Parameter(type, "s");
//s.country for example
var property = Expression.Property(parameter, propertyName);
//string.Contains method
var containsMethod = typeof (string).GetMethod("Contains", new[] {typeof (string)});
//s.country.Contains(<yoursearchvalue>)
var expression = Expression.Call(property, containsMethod, Expression.Constant(searchValue));
//s => s.country.Contains(<yoursearchvalue>)
var lambda = Expression.Lambda<Func<T, bool>>(expression, parameter);
//filter your queryable with predicate
return queryable.Where(lambda);
}
}
usage
var fieldtosearch = "country";
entries = entries.Filter(fieldtosearch , searchString);
In general, other than using dynamic linq queries, you could also use Expression to build a generic method to construct a property Getter and use it in your linq query, a quick sampel:
public static Func<TObject, TProperty> GetPropGetter<TObject, TProperty>(string propertyName)
{
ParameterExpression paramExpression = Expression.Parameter(typeof(TObject), "value");
Expression propertyGetterExpression = Expression.Property(paramExpression, propertyName);
Func<TObject, TProperty> result =
Expression.Lambda<Func<TObject, TProperty>>(propertyGetterExpression, paramExpression).Compile();
return result;
}
to use it:
var getter = GetPropGetter<YourEntity, string>("Name");
var found = entites.Where(m => getter(m).Contains("someInput"));

Converting List<string> to EntityFramework column/field list

Using EntityFramework, I can get a list of entities of a certain type using the following syntax:
List<Customer> customers = ((IQueryable<Customer>)myEntities.Customers
.Where(c => c.Surname == strSurname)
.OrderBy(c => c.Surname)).ToList<Customer>();
I can then do something like this to end up with only the data I'm interested in:
var customerSummaries = from s in customers
select new
{
s.Surname, s.FirstName, s.Address.Postcode
};
I'm given a list of strings (based on user selection) of the fields (and tables where necessary) that comprise the requested summarized data. Eg for the above 'customerSummary' the list of strings provided would be: "Surname", "FirstName", "Address.Postcode".
My question is: How do I convert that list of strings into the syntax needed to extract only the specified fields?
If that can't be done, what would be a better type (than string) for the list of columns so I can extract the right info?
I guess I need to know the type that an EF [entity|table]'s [member|column|field] is, if that makes sense.
EDIT:
I tried the suggested answer - dynamic linq - using the following syntax
string parmList = "Surname, Firstname, Address.Postcode";
var customers = myEntities.Customers.Select(parmList)
.OrderBy("Address.Postcode");
but this results in: EntitySqlException was unhandled. 'Surname' could not be resolved in the current scope or context. Make sure that all referenced variables are in scope, that required schemas are loaded, and that namespaces are referenced correctly.
So, a follow-up question. Am I using Select properly? I have only seen examples using the Where and OrderBy clauses, but I think I'm doing it right based on those.
If my Select syntax is not the problem, can anyone see what is?
Edit 2: It was upside down. This works:
string parmList = "Surname, Firstname, Address.Postcode";
var customers = myEntities.Customers
.OrderBy("Address.Postcode")
.Select(parmList);
You can use dynamic linq, check it in here. It is also available on nuget
I suggest dynamic linq as #Cuong already posted.
But for simple Select projections and as a hand-made exercise in expressions...
Using LinqRuntimeTypeBuilder from How to create LINQ Expression Tree to select an anonymous type
(see the comments there for 'why' is that necessary)
And add this...
public static IQueryable SelectDynamic(this IQueryable source, IEnumerable<string> fieldNames)
{
Dictionary<string, PropertyInfo[]> sourceProperties = new Dictionary<string, PropertyInfo[]>();
foreach (var propertyPath in fieldNames)
{
var props = propertyPath.Split('.');
var name = props.Last();
PropertyInfo[] infos;
if (sourceProperties.TryGetValue(name, out infos))
name = string.Join("", props);
sourceProperties[name] = source.ElementType.GetDeepProperty(props);
}
Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.ToDictionary(x => x.Key, x => x.Value.Last().PropertyType));
ParameterExpression sourceItem = Expression.Parameter(source.ElementType, "t");
IEnumerable<MemberBinding> bindings = dynamicType.GetFields()
.Select(p => Expression.Bind(p, sourceItem.MakePropertyExpression(sourceProperties[p.Name]))).OfType<MemberBinding>();
Expression selector = Expression.Lambda(Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);
MethodCallExpression selectExpression = Expression.Call(typeof(Queryable), "Select", new Type[] { source.ElementType, dynamicType }, Expression.Constant(source), selector);
return Expression.Lambda(selectExpression).Compile().DynamicInvoke() as IQueryable;
}
public static PropertyInfo[] GetDeepProperty(this Type type, params string[] props)
{
List<PropertyInfo> list = new List<PropertyInfo>();
foreach (var propertyName in props)
{
var info = type.GetProperty(propertyName);
type = info.PropertyType;
list.Add(info);
}
return list.ToArray();
}
public static Expression MakePropertyExpression(this ParameterExpression sourceItem, PropertyInfo[] properties)
{
Expression property = sourceItem;
foreach (var propertyInfo in properties)
property = Expression.Property(property, propertyInfo);
return property;
}
Use it like e.g.:
public static IEnumerable<object>
SelectAsEnumerable(this IQueryable entitySet, params string[] propertyPath)
{
return entitySet.SelectDynamic(propertyPath) as IEnumerable<object>;
}
var list = db.YourEntity.SelectAsEnumerable("Name", "ID", "TestProperty.ID").ToList();
Note:
You can use similar approach to expand for OrderBy etc. - but this wasn't meant to cover all angles (or any real queries)
Deep Property Expressions from this post of mine

dynamic query using expression tree

I have a form in which the user will choose the following from dropdown lists:
table_name
columnName_to_sort_by
columnName_to_search_in
The user shall enter Search_text in a text box
The form shall draw data from many tables. I want to avoid writing the sort and search for every field for each of the tables. This is why I want to use expression trees. I want to build the query dynamically.
I want to write a generic method that will generate the expression tree for the select, where and orderby methods, depending on user input. I can use System.Reflection to get the Type that is being queried (all my tables are types - I am using LinqToSql).
I do not know how to form the expression trees.
Here's what I have so far:
private static List<T> GetSortedData<T>( string sortColumnName)
{
var type = typeof(T);
var property = type.GetProperty(sortColumnName);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderBy", new Type[] { type, property.PropertyType }, WHAT_SHOULD_BE_HERE, Expression.Quote(orderByExp));
return (List<T>)Expression.Lambda(resultExp).Compile().DynamicInvoke();
}
How can I implement select, sort and orderby dynamically using expression trees?
What you have is close. Where you ask, "WHAT_SHOULD_BE_HERE", you are curious what expression to use to indicate the "source" parameter for OrderBy, which is usually implied from the operand when used as an extension method. What you need to do is change your sample to operate on IQueryable, and you need to accept that as an input parameter. Also, replace your WHAT_SHOULD_BE_HERE placeholder with "list.Expression" as shown below.
private static IEnumerable<T> GetSortedData<T>(IQueryable<T> list, string sortColumnName)
{
var type = typeof(T);
var property = type.GetProperty(sortColumnName);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.Property(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderBy", new[] { type, property.PropertyType }, list.Expression, Expression.Quote(orderByExp));
return (IEnumerable<T>)Expression.Lambda(resultExp).Compile().DynamicInvoke();
}
I tested this with the following code:
static void Main(string[] args)
{
var list = new List<Person>(new[]
{
new Person { FirstName = "John" },
new Person { FirstName = "Jane" }
}).AsQueryable();
foreach (var o in GetSortedData(list, "FirstName"))
Console.WriteLine(o.FirstName);
}
public class Person
{
public string FirstName { get; set; }
}
Which printed out:
Jane
John
I was facing the same Error with Order by.
I looked into the Call method and found that I was missing the Parameter -- list.Expression, where list is your IQuerable

Categories