I am creating a custom reporting tool that allows the user to enter a list of criteria.
public partial class CustomReportCriteria
{
public int Id { get; set; }
public int ReportId { get; set; }
public string Operator { get; set; }
public string CriteriaValue { get; set; }
public string CriteriaType { get; set; }
public string CriteriaField { get; set; }
public string PropertyType { get; set; }
public virtual CustomReport CustomReport { get; set; }
}
I need to utilize these fields to create dynamic queries on my database. For example:
Select BidYear From FWOBid Where BidYear = 2015
// ^ this would look like this instead
Select CriteriaField From PropertyType Where CriteriaField [Operator] CriteriaValue
However, I may not always be querying the same table, and in most cases the query will require joins on other tables.
I already have generic methods for the where clauses:
public static IQueryable<T> Filter<T>(IQueryable<T> query, string propertyName, string propertyValue, string op)
{
PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName);
return Filter<T>(query, propertyInfo, propertyValue, op);
}
public static IQueryable<T> Filter<T>(IQueryable<T> query, PropertyInfo propertyInfo, string propertyValue, string op)
{
ParameterExpression e = Expression.Parameter(typeof(T), "e");
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
UnaryExpression c = GetExpressionByType(propertyValue, propertyInfo.PropertyType);
BinaryExpression b = null;
if (op == "=")
{
b = Expression.Equal(m, c);
}
else if (op == "!=")
{
b = Expression.NotEqual(m, c);
}
else if (op == ">")
{
b = Expression.GreaterThan(m, c);
}
else if (op == "<")
{
b = Expression.LessThan(m, c);
}
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(b, e);
return query.Where(lambda);
}
The approach I was envisioning was something like this:
foreach (var crit in report.CustomReportCriterias)
{
var objectName = crit.PropertyType;
var value = crit.CriteriaValue;
var type = crit.CriteriaType;
var field = crit.CriteriaField;
var op = crit.Operator;
// how to dynamically get a generic collection ?
var queryable = _context.Set<objectName>();
var results = Filter<objectName>(queryable, field, value, op);
// would then do joins if needed
}
But, I'm struggling with the initial steps of retrieving a queryable from the db based on a string, and then I'm lost as how to join these results after.
Using Entity Framework you can execute a query directly and have the results map to an entity like this.
var results = db.ExecuteStoreQuery<YourEntity>(
"SELECT * FROM YourEntities WHERE SomeColumn = #param1",
param1);
Have a look at https://dynamiclinq.codeplex.com/releases/view/109593. This library extends the Linq library with a System.Linq.Dynamic namespace that provides string-based overloads to common query methods, so you can dynamically build string-based criteria for filtering and grouping. In addition, the one in my link is one better than the version available through NuGet, because it allows you to define the entire query, including the record type to retrieve, in Linq syntax as one string (see the Documentation tab for one example; it uses a static typeof expression to provide a Type to parse the results into, but you can reflectively create generic types with Type.MakeGenericType()).
The problem's going to be getting back to a statically-defined type; once you go dynamic, you tend to have to stay dynamic, using GetProperties() to enumerate data fields and GetValue/SetValue to manipulate them. There are a few tricks, typically requiring the use of a set of concretely-typed overloads that force the runtime to examine the true type of the object, then once the object's passed into one of those overloads you're back in static world again.
Related
Let's say I have a simple model:
public class Movie
{
public int ID { get; set; }
public string Name { get; set; }
}
And a DbContext:
public class MoviesContext : DbContext
{
...
public DbSet<Movie> Movies { get; set; }
}
Also I have a method in MoviesContext class that filters Movies by substring like this:
return Movies.Where(m => m.Name.Contains(filterString)).Select(m => m);
Now suppose I'd like to add a new model, say:
public class Person
{
public int ID { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string FullName { get { return FirstName + (MiddleName?.Length > 0 ? $" {MiddleName}" : "") + $" {LastName}"; } }
}
I also want to filter persons (DbSet Persons) by name (i.e. FullName). I'd like DRY, so it is preferrable to generalize a filter method of MoviesContext. And, what is important, I'd like to do filtering on the database level. So I have to deal with LINQ for Entities.
If not for this, the task is pretty simple. I could use an abstract class and add a virtual method that do the "contains substring" logic. Alternatively, I could use an interface. Unfortunately, because of LINQ for Entities, I can't use a FullName property (which is not convenient but bearable) and I can't write something like this:
return dbset.Where(ent => ent.NameContains(filterString)).Select(ent => ent);
So, how to solve this problem? I've found some solution (almost have my head broken), but I am not very happy with it. I'll post my solution separately, but I hope there is a more elegant one.
Reading your code a little more closely, instead of your NameFilterable abstract class, could you not do something like this:
public interface IHasPredicateGetter<T> {
[NotNull] Expression<Func<T, bool>> GetPredicateFromString([NotNull] string pValue);
}
public class Movie : IHasPredicateGetter<Movie> {
public int ID { get; set; }
public string Name { get; set; }
public Expression<Func<Movie, bool>> GetPredicateFromString(string pValue) {
return m => m.Name.Contains(pValue);
}
}
This prevents you from needing a cast, for example. It's so hard to grasp just what you're trying to do here, so I'm not sure this is the whole thing or not. You're still stuck with an instance method that should probably be a static method, but it couldn't implement an interface otherwise.
My solution looks like this.
[1] The base class:
public abstract class NameFilterable
{
protected static Expression<Func<T, bool>> False<T>() { return f => false; }
public virtual Expression<Func<T, bool>> GetNameContainsPredicate<T>(string filterString)
{
return False<T>();
}
}
[2] The Person class (I'll omit the Movie class, it is more simple):
public class Person : NameFilterable
{
...
public override Expression<Func<T, bool>> GetNameContainsPredicate<T>(string filterString)
{
return entity =>
String.IsNullOrEmpty(filterString) ||
(entity as Person).LastName.Contains(filterString) ||
(entity as Person).FirstName.Contains(filterString) ||
(((entity as Person).MiddleName != null) && (entity as Person).MiddleName.Contains(filterString))
;
}
}
[3] The filter methods in MoviesContext:
private static IQueryable<T> _filterDbSet<T>(DbSet<T> set, Expression<Func<T, bool>> filterPredicate) where T : class
{
return set
.Where(filterPredicate)
.Select(ent => ent);
}
private static IQueryable<T> _filterDbSet<T>(DbSet<T> set, string search = null) where T : NameFilterable, new()
{
T ent = new T();
return _filterDbSet<T>(set, (ent as NameFilterable).GetNameContainsPredicate<T>(search));
}
public static ICollection<T> Filter<T>(DbSet<T> set, string search = null) where T : NameFilterable, new()
{
return _filterDbSet(set, search).ToList();
}
And it seems that all this works pretty well. But I can't say it is very elegant.
[1] I have to use a generic T, though on the Person level I always work with Person objects (or descendants). So I have to convert T to Person (as Person).
[2] In GetNameContainsPredicate method, I can't write (because of LINQ for Entities):
return entity =>
{
Person p = entity as Person;
String.IsNullOrEmpty(filterString) ||
p.LastName.Contains(filterString) ||
p.FirstName.Contains(filterString) ||
((p.MiddleName != null) && p.MiddleName.Contains(filterString))
};
[3] I can't use static methods (statics couldn't be overridden), so I have to create a dummy T object (T ent = new T();).
[4] I still can't use a FullName.Contains(filterString)
So, the question remains: Maybe I miss something and there is a more elegant solution to the problem?
You could create a method that is responsible for search if a type has a particular property and filter for that property,if the object has not the property simply return null. Whith this you can create an expression that filters for this property
//gets the property info of the property with the giving name
public static PropertyInfo GetPropetyInfo<T>(string name)
{
var type = typeof(T);
var property = type.GetProperty(name);
return property;
}
//Creates an expression thats represents the query
public static Func<T, bool> GetFilterExpression<T>( string propertyName, object propertyValue)
{
var prop = GetPropetyInfo<T>(propertyName);
if(prop==null)return t=>false;
var parameter = Expression.Parameter(typeof(T), "t");
Expression expression = parameter;
var left = Expression.Property(expression, prop);
if (prop.PropertyType == typeof(string))
{
var toLower = typeof(string).GetMethods().FirstOrDefault(t => t.Name.Equals("ToLower"));
var tlCall = Expression.Call(left, toLower);
var right = Expression.Constant(propertyValue.ToString().ToLower());
var contains = Expression.Call(tlCall, typeof(string).GetMethod("Contains"), right);
var containsCall = Expression.IsTrue(contains);
expression = Expression.AndAlso(Expression.NotEqual(left, Expression.Constant(null)), containsCall);
}
else
{
if (prop.PropertyType.ToString().ToLower().Contains("nullable"))
{
var getValue = prop.PropertyType.GetMethods().FirstOrDefault(t => t.Name.Equals("GetValueOrDefault"));
var getValueCall = Expression.Call(left, getValue);
var right = Expression.Constant(propertyValue);
expression = Expression.Equal(getValueCall, right);
}
else
{
var value = Convert.ChangeType(propertyValue,prop.PropertyType);
var right = Expression.Constant(value);
expression = Expression.Equal(left, right);
}
}
return Expression.Lambda<Func<T, bool>>(expression, new ParameterExpression[] { parameter }).Compile();
}
The you can use it as follow
var expression = YOURCLASS.GetFilterExpression<Person>("LastName", "Jhon");
var result=dbset.Where(expression);
There are a few things I've done to get polymorphism with EF, but in your specific case of wanting reusable filters, I'm not sure it's worth the trouble. I've basically tried to do the same exact thing, but every time I end up realizing that there's no point. Ask yourself: what exactly are the benefits of doing this, and how is it any more flexible than what a Where clause already offers?
There are two issues. One is that it's hard or nigh impossible to get a filter to be used between two separate classes by using a shared interface (INamedObject for example). This is because you need a strongly typed expression. You can make a function that returns a strongly typed expression, but why would you not have just wrote the expression in the first place? The other issue is that you need a new filter expression for every search value, which is pretty close to where we are already.
If you perfected this, what would you have? The ability to infer type, specify a search value, and get an expression you could use? Isn't that essentially what we already have? The way Where clauses already are, they already have strong typing, and the ability to use dynamic search values. While it might feel a tiny bit redundant to say x => x.Name == value in more than one place, really the ability to specify such a concise and powerful filter statement is already a pretty amazing place to be.
Having spent a long time solving this problem, I wanted to share the solution.
Background
I maintain a large web application with the primary function of managing orders. It is an MVC over C# application using EF6 for data.
There are LOTS of search screens. The search screens all have multiple parameters and return different object types.
The Problem
Every search screen had:
A ViewModel with the search parameters
A Controller method to handle the Search event
A method to pull the correct data for that screen
A method to apply all the search filters to the dataset
A method to convert the results into a NEW results ViewModel
The Results ViewModel
This adds up quickly. We have about 14 different search screens, which means about 84 models & methods to handle these searches.
My Goal
I wanted to be able to create a class, analogous to the current search parameter ViewModel, that would inherit from a base SearchQuery class such that my Controller could simply trigger the search to run to populate a Results field of the same object.
An Example of My Ideal State (Because It's a Bear To Explain)
Take the following class structure:
public class Order
{
public int TxNumber;
public Customer OrderCustomer;
public DateTime TxDate;
}
public class Customer
{
public string Name;
public Address CustomerAddress;
}
public class Address
{
public int StreetNumber;
public string StreetName;
public int ZipCode;
}
Let's assume I have lots of those records in a queryable format--an EF DBContext object, an XML object, whatever--and I want to search them. First, I create a derived class specific to my ResultType(in this case, Order).
public class OrderSearchFilter : SearchQuery
{
//this type specifies that I want my query result to be List<Order>
public OrderSearchFilter() : base(typeof(Order)) { }
[LinkedField("TxDate")]
[Comparison(ExpressionType.GreaterThanOrEqual)]
public DateTime? TransactionDateFrom { get; set; }
[LinkedField("TxDate")]
[Comparison(ExpressionType.LessThanOrEqual)]
public DateTime? TransactionDateTo { get; set; }
[LinkedField("")]
[Comparison(ExpressionType.Equal)]
public int? TxNumber { get; set; }
[LinkedField("Order.OrderCustomer.Name")]
[Comparison(ExpressionType.Equal)]
public string CustomerName { get; set; }
[LinkedField("Order.OrderCustomer.CustomerAddress.ZipCode")]
[Comparison(ExpressionType.Equal)]
public int? CustomerZip { get; set; }
}
I use attributes to specify what field/property of the target ResultType any given search field is linked to, as well as the comparison type (== < > <= >= !=). A blank LinkedField means that the name of the search field is the same as the name of the target object field.
With this configured, the only things I should need for a given search are:
A populated search object like the one above
A data source
No other scenario-specific coding should be required!
The Solution
For starters, we create:
public abstract class SearchQuery
{
public Type ResultType { get; set; }
public SearchQuery(Type searchResultType)
{
ResultType = searchResultType;
}
}
We'll also create the attributes we used above to define the search field:
protected class Comparison : Attribute
{
public ExpressionType Type;
public Comparison(ExpressionType type)
{
Type = type;
}
}
protected class LinkedField : Attribute
{
public string TargetField;
public LinkedField(string target)
{
TargetField = target;
}
}
For each search field, we'll need to know not only WHAT search is done, but also WHETHER the search is done. For example, if the value of "TxNumber" is null, we wouldn't want to run that search. So we create a SearchField object that contains, in addition to the actual search value, two expressions: one that represents performing the search, and one that validates whether the search should be applied.
private class SearchFilter<T>
{
public Expression<Func<object, bool>> ApplySearchCondition { get; set; }
public Expression<Func<T, bool>> SearchExpression { get; set; }
public object SearchValue { get; set; }
public IQueryable<T> Apply(IQueryable<T> query)
{
//if the search value meets the criteria (e.g. is not null), apply it; otherwise, just return the original query.
bool valid = ApplySearchCondition.Compile().Invoke(SearchValue);
return valid ? query.Where(SearchExpression) : query;
}
}
Once we have created all our filters, all we need to do is loop through them and call the "Apply" method on our dataset! Easy!
The next step is creating the validation expressions. We'll do this based on the Type; every int? is validated the same as every other int?.
private static Expression<Func<object, bool>> GetValidationExpression(Type type)
{
//throw exception for non-nullable types (strings are nullable, but is a reference type and thus has to be called out separately)
if (type != typeof(string) && !(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)))
throw new Exception("Non-nullable types not supported.");
//strings can't be blank, numbers can't be 0, and dates can't be minvalue
if (type == typeof(string )) return t => !string.IsNullOrWhiteSpace((string)t);
if (type == typeof(int? )) return t => t != null && (int)t >= 0;
if (type == typeof(decimal? )) return t => t != null && (decimal)t >= decimal.Zero;
if (type == typeof(DateTime?)) return t => t != null && (DateTime?)t != DateTime.MinValue;
//everything else just can't be null
return t => t != null;
}
This was all I needed for my application, but there is definitely more validation that could be done.
The search expression is slightly more complicated and required a parser to "De-qualify" Field/Property names (there's probably a better word, but if so, I don't know it). Basically, if I specified "Order.Customer.Name" as a linked field and I'm searching through Orders, I need to turn that into "Customer.Name" because there is no Order Field inside an Order object. Or at least I hope not. :) This isn't certain, but I considered it better to accept and correct fully-qualified object names than to support that edge case.
public static List<string> DeQualifyFieldName(string targetField, Type targetType)
{
var r = targetField.Split('.').ToList();
foreach (var p in targetType.Name.Split('.'))
if (r.First() == p) r.RemoveAt(0);
return r;
}
This is just straight text parsing, and returns the Field name in "levels" (e.g. "Customer"|"Name").
All right, let's get our search expression together.
private Expression<Func<T, bool>> GetSearchExpression<T>(
string targetField, ExpressionType comparison, object value)
{
//get the property or field of the target object (ResultType)
//which will contain the value to be checked
var param = Expression.Parameter(ResultType, "t");
Expression left = null;
foreach (var part in DeQualifyFieldName(targetField, ResultType))
left = Expression.PropertyOrField(left == null ? param : left, part);
//Get the value against which the property/field will be compared
var right = Expression.Constant(value);
//join the expressions with the specified operator
var binaryExpression = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(binaryExpression, param);
}
Not so bad! What we're trying to create is, for example:
t => t.Customer.Name == "Searched Name"
Where t is our ReturnType--an Order, in this case. First we create the parameter, t. Then, we loop through the parts of the property/field name until we have the full title of the object we're targeting (naming it "left" because it's the left side of our comparison). The "right" side of our comparison is simple: the constant provided by the user.
Then we create the binary expression and turn it into a lambda. Easy as falling off a log! If falling off a log required countless hours of frustration and failed methodologies, anyway. But I digress.
We've got all the pieces now; all we need is a method to assemble our query:
protected IQueryable<T> ApplyFilters<T>(IQueryable<T> data)
{
if (data == null) return null;
IQueryable<T> retVal = data.AsQueryable();
//get all the fields and properties that have search attributes specified
var fields = GetType().GetFields().Cast<MemberInfo>()
.Concat(GetType().GetProperties())
.Where(f => f.GetCustomAttribute(typeof(LinkedField)) != null)
.Where(f => f.GetCustomAttribute(typeof(Comparison)) != null);
//loop through them and generate expressions for validation and searching
try
{
foreach (var f in fields)
{
var value = f.MemberType == MemberTypes.Property ? ((PropertyInfo)f).GetValue(this) : ((FieldInfo)f).GetValue(this);
if (value == null) continue;
Type t = f.MemberType == MemberTypes.Property ? ((PropertyInfo)f).PropertyType : ((FieldInfo)f).FieldType;
retVal = new SearchFilter<T>
{
SearchValue = value,
ApplySearchCondition = GetValidationExpression(t),
SearchExpression = GetSearchExpression<T>(GetTargetField(f), ((Comparison)f.GetCustomAttribute(typeof(Comparison))).Type, value)
}.Apply(retVal); //once the expressions are generated, go ahead and (try to) apply it
}
}
catch (Exception ex) { throw (ErrorInfo = ex); }
return retVal;
}
Basically, we just grab a list of fields/properties in the derived class (that are linked), create a SearchFilter object from them, and apply them.
Clean-Up
There's a bit more, of course. For example, we're specifying object links with strings. What if there's a typo?
In my case, I have the class check whenever it spins up an instance of a derived class, like this:
private bool ValidateLinkedField(string fieldName)
{
//loop through the "levels" (e.g. Order / Customer / Name) validating that the fields/properties all exist
Type currentType = ResultType;
foreach (string currentLevel in DeQualifyFieldName(fieldName, ResultType))
{
MemberInfo match = (MemberInfo)currentType.GetField(currentLevel) ?? currentType.GetProperty(currentLevel);
if (match == null) return false;
currentType = match.MemberType == MemberTypes.Property ? ((PropertyInfo)match).PropertyType
: ((FieldInfo)match).FieldType;
}
return true; //if we checked all levels and found matches, exit
}
The rest is all implementation minutia. If you're interested in checking it out, a project that includes a full implementation, including test data, is here. It's a VS 2015 project, but if that's an issue, just grab the Program.cs and Search.cs files and throw them into a new project in your IDE of choice.
Thanks to everyone on StackOverflow who asked the questions and wrote the answers that helped me put this together!
I wanted to store a collection of expressions accessing object's properties. For example:
class Entity
{
public int Id { get; set; }
public Entity Parent { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
public decimal Value { get; set; }
public bool Active { get; set; }
}
static void Main(string[] args)
{
var list = new List<Expression<Func<Entity, object>>>();
list.Add(e => e.Id);
list.Add(e => e.Name);
list.Add(e => e.Parent);
list.Add(e => e.Date);
list.Add(e => e.Value);
list.Add(e => e.Active);
StringBuilder b = new StringBuilder();
list.ForEach(f => b.AppendLine(f.ToString()));
Console.WriteLine(b.ToString());
Console.ReadLine();
}
This code outputs:
e => Convert(e.Id)
e => e.Name
e => e.Parent
e => Convert(e.Date)
e => Convert(e.Value)
e => Convert(e.Active)
It does add Convert to value types.
As far as in the end I wanted to use those expressions with LINQ to SQL, I need not to have that Convert in expressions, for them to be successfully translated to SQL.
How can I achieve this?
P.S.: expressions from this collection are later used as arguments to OrderBy and ThenBy methods.
If you create a function generic in the proeprty type you can avoid the Convert:
private static LambdaExpression GetExpression<TProp>
(Expression<Func<Entity, TProp>> expr)
{
return expr;
}
then you can change the type of list:
var list = new List<LambdaExpression>();
list.Add(GetExpression(e => e.Id));
list.Add(GetExpression(e => e.Name));
This will require you to create your OrderBy and ThenBy expressions using reflection e.g.
LambdaExpression idExpr = list[0];
Type keyType = idExpr.ReturnType;
var orderByMethod = typeof(Queryable).GetMethods()
.Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(Entity), keyType);
var ordered = (IQueryable<Entity>)
orderByMethod.Invoke(null, new object[] { source, idExpr });
I patched up a EF code first attempt at using your code like this
public class Entity
{
public int Id { get; set; }
public Entity Parent { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
public decimal Value { get; set; }
public bool Active { get; set; }
}
public class EntityContext : DbContext
{
public EntityContext()
: base(new SqlCeConnection("Data Source=Database.sdf;Persist Security Info=False;"),
contextOwnsConnection: true)
{
// Using a SQL Compact database as backend
}
public DbSet<Entity> Entities { get; set; }
}
and attempted some linq on the context
static void Main(string[] args)
{
var list = new List<Expression<Func<Entity, object>>>();
list.Add(e => e.Date);
list.Add(e => e.Name);
using (var c = new EntityContext())
{
//each time a new record is added
var data = new Entity
{
Name = string.Format("Data{0}", c.Entities.Count()),
Date = DateTime.Now
};
c.Entities.Add(data);
c.SaveChanges();
// sort by date
foreach (var e in c.Entities.OrderBy(list.First().Compile()))
Console.WriteLine(string.Format("{0} - {1}", e.Name, e.Date));
// sort by name .. in reverse
foreach (var e in c.Entities.OrderByDescending(list.Last().Compile()))
Console.WriteLine(string.Format("{0} - {1}", e.Name, e.Date));
}
Console.ReadLine();
}
There were no issues running the code.
UPDATE The same holds true for LINQ to SQL: I built a table in a local SQL Server with the same structure as the class, and tried to OrderBy it : no problem.
My answer is "You don't need to worry about that".
Thank's to the answer by Alex I found out for myself that, when ordering the data I can use two different methods, depending on the specified argument:
Queryable.OrderBy Method with Expression<Func<TSource, TKey>>
Enumerable.OrderBy Method with Func<TSource, TKey>
When Queryable.OrderBy is used, LINQ compiles the OrderBy clause into the SQL statement, executed over the database. So when I try to give it a Expression<Func<TEntity, object>> that looks like e => Convert(e.Field), LINQ throws an InvalidOperationException, saying Cannot order by type 'System.Object'.
When Enumerable.OrderBy is used, LINQ does not compile the OrderBy clause into the SQL query, but executes the current query and applies sorting on the enumerable of entities, returned by the query, in the program's memory. Here no problem with ordering by Func<TEntity, object>.
So I found two alternatives here:
Query the database without sorting and order the returned result set
Provide better expressions to LINQ, that it could compile the SQL query, and then apply sorting in the database layer; here the answer by Lee suggests one way..
In my exact case sorting is the last operation to execute, and I don't see much harm, if I order the result set in the programm's memory...I'm not going to expect huge amounts of data to be returned...
Though in a more common case, probably it's still better to do all possible operations in the database layer...
P.S.: SO: Order a linq query - a close discussion...
With the ListBox control it is possible to feed it a DataSource, name a DisplayMember and a ValueMember and through some magic it will display a field from the DataSource and return a selected ValueMember. It can work wit a linq-to-sql result without even knowing anyting specific about the table it is feed with.
Isn't Reflection and Attributes doing some magic? How does it work!
I have a need to do something similar but I do not know where to start. I'm a beginner for LINQtoSQL.
This is what I want to do.
I have a source table that I want to filter. The source table can be anything but will be originating from some DataContext.
var MySourceTable =
from MyRecord in Context.GetTable<MySourceTable>()
select new
{
Value = MyRecord.ID,
Display = MyRecord.Name,
FilterValue = MyRecord.Value
};
In my control I want to be able to filter MySourceTable on some given value. The control does not know what table is used (MySourceTable in the example above) and the control does only know the three names, ID, Name and Value of the fields in the record it should use.
The filter query should look like the example below.
var MyTable
from Record in MySourceTable
where FilterValue == GivenValue
select new
{
Value = Record.ID,
Display = Record.Name,
};
Can somebody advise me on where to start?
It looks like what you are missing is in the where condition on your query. It should look like this:
var MyTable =
from Record in MySourceTable
where Record.FilterValue == GivenValue
select new
{
Value = Record.ID,
Display = Record.Name,
};
GivenValue is presumably a local variable or property containing whatever you want to compare FilterValue against. But FilterValue is a property of the anonymous type that you created in your first query that created MySourceTable. In your second query, Record is an instance of that anonymous type, and you have to use that reference to the instance in all other parts of the query to reference the instance that you are checking for the where clause or selecting for the select clause. If you just put FilterValue there, then it has no idea what you mean.
I wrote a filter engine that takes in a Property and Value as a string, and is able to use that as a where clause.
IQueryable<T> FilterFunction<T>(IQueryable<T> query)
{
ParameterExpression p = Expression.Parameter(typeof(T), "notused");
Expression<Func<T, bool>> wherePredicate =
Expression.Lambda<Func<T, bool>>(
Expression.Equal(
Expression.Call(Expression.Property(p, FilterProperty), "ToString", new Type[0]),
Expression.Constant(FilterValue)), p);
return query.Where(wherePredicate);
}
You should be able to pass in an Expression<Func<T, TResult>> built in a similar way into query.Select()
If I am understanding your question correctly, I believe this will work:
string DisplayProperty = "Name";
string ValueProperty = "ID";
IQueryable<Record> SelectRecordProperties<T>(IQueryable<T> query)
{
ParameterExpression p = Expression.Parameter(typeof(T), "notused");
MethodInfo ctorMethod = typeof(Record).GetMethod("Create");
Expression<Func<T, Record>> selectPredicate =
Expression.Lambda<Func<T, Record>>(
Expression.Call(ctorMethod,
Expression.PropertyOrField(p, DisplayProperty),
Expression.PropertyOrField(p, ValueProperty)), p);
return query.Select(selectPredicate);
}
class Record
{
public static Record Create(string display, string value)
{
return new Record() { Display = display, Value = value };
}
public object Display { get; set; }
public object Value { get; set; }
}
So for your full function you'd need to combine these two ideas so that your filtering works.
By the way, there are many possible ways to build the expression tree for this, there was some tool I've found at one point which would show you the expression tree I think, so you could manually write the linq query and see how .Net builds the expression, then modify this code to build it based on that to possibly get a more efficient expression tree.
I found a way, it works but is not a completely satisfying method.
The 'problem' (compared to my original question) is that it does not use linq-to-sql for filtering the source. But it works and that is at the moment fine for me.
Recap:
At a high level module I prepare a LINQtoSQL statement which will result in some IQueriable<Object>, effectively an IQueriable object. This object is given to a lower level module together with three names, one for the ID, one for the Display and one for filtering.
When I need more control over the source, e.g. for unfiltered data that will result in huge results or for complex LINQtoSQL queries I will use a delegate function. That will give me all the control I want.
At a low level I have a IQueriable<Object> and no knowledge at all about the Object, except for wat Reflection can tell me. I generate a table of results that I want to use in a control-specific format. (the Record class). For anything more complex that my standard code cannot handle I offer a delegate that must result in some list of object in which the object must have at least a property named 'Display' and a property named 'Value'. Other properties are possible but will not be used.
This is the solution that I finaly got to work:
public partial class MySelector : UserControl
{
class Record
{
public object Display { get; set; }
public object Value { get; set; }
}
....
public string MyDisplayMember { get; set; }
public string MyValueMember { get; set; }
public string MyExternalMember { get; set; }
....
static Object Filter(MySelector sender, Object criterium)
{
IQueryable source = sender.MySource as IQueryable;
if (source == null) return null;
List<Record> result = new List<Record>();
// drawback: this foreach loop will trigger a unfiltered SQL command.
foreach (var record in source)
{
MethodInfo DisplayGetter = null;
MethodInfo ValueGetter = null;
bool AddRecord = false;
foreach (PropertyInfo property in record.GetType().GetProperties())
{
if (property.Name == sender.MyDisplayMember)
{
DisplayGetter = property.GetGetMethod();
}
else if (property.Name == sender.MyValueMember)
{
ValueGetter = property.GetGetMethod();
}
else if (property.Name == sender.MyExternalMember)
{
MethodInfo ExternalGetter = property.GetGetMethod();
if (ExternalGetter == null)
{
break;
}
else
{
object external = ExternalGetter.Invoke(record, new object[] { });
AddRecord = external.Equals(criterium);
if (!AddRecord)
{
break;
}
}
}
if (AddRecord && (DisplayGetter != null) && (ValueGetter != null))
{
break;
}
}
if (AddRecord && (DisplayGetter != null) && (ValueGetter != null))
{
Record r = new Record();
r.Display = (DisplayGetter == null)
? null
: DisplayGetter.Invoke(record, new object[] { });
r.Value = (ValueGetter == null)
? null
: ValueGetter.Invoke(record, new object[] { });
result.Add(r);
}
}
return result;
}
}
I'm trying to autogenerate the parameter to IQueryable.Where so I can select entities from my entity framework code first data context without writing and wiring up a lot of tedious mapping code.
My project contains a bunch of DTOs that look like:-
class FooDto
{
public string SomeProperty { get; set; }
public string SomeOtherProperty { get; set; }
}
And a bunch of entities that look like:-
class Foo
{
public string SomeProperty { get; set; }
public string SomeOtherProperty { get; set; }
// Some other properties here.
}
The DTO contains the fields necessary to identify some subset of entities in my database. I use the DTOs to query an IQueryable of entities:-
var result = queryable.Where(
x => x.SomeProperty == dto.SomeProperty
&& x.SomeOtherProperty == dto.SomeOtherProperty)
The actual properties vary, but the queries are always of the shape "Where all of the properties on the entity match all of the matching properties on the DTO". There's no more complicated query object functionality going around.
There are many dozens of DTOs and entities. Creating/maintaining and wiring up all of these predicates is a challenging architectural issue. We're currently using the strategy pattern:-
public class FooDtoSelectStrategy : ISelectStrategy<FooDto, FooEntity>
{
public Func<FooEntity, bool> GetPredicate(FooDto dto)
{
return x => x.SomeProperty == dto.SomeProperty
&& x.SomeOtherProperty == dto.SomeOtherProperty;
}
}
Along with a pile of ninject bindings, but we've got some few dozens of these already and we're looking at hundreds more as our domain expands.
We had a similar challenge mapping values from the entity to the DTO which we resolved using AutoMapper.
Can automapper (or a similar tool) create these predicates and allow us to implement a single GenericPredicateProvider<TDto, TEntity>?
What you're doing is actually called the specification pattern. So you've basically re-engineered the wheel, coming up with a slightly different wheel that is basically the same thing.
You could use something like T4 templates to auto-generate these Predicate providers, but it seems kind of silly that you are querying an object for the identical object. You already have the object, so why are you querying all the fields for the exact same thing?
Update:-
Here's my prototype using AutoMapper's type mapping to handle the simple case. It'll need more work later if I want it to handle e.g. complex type mapping, but for now this is doing the job.
internal class AutoMapperSelectStrategy<TEntity, TIdentity> :
IIdentitySelectStrategy<TEntity, TIdentity>
{
private IMappingEngine mappingEngine;
internal AutoMapperSelectStrategy(
IMappingEngine<TEntity, TIdentity> mappingEngine)
{
this.mappingEngine = mappingEngine;
}
public Expression<Func<TEntity, bool>> GetPredicateForIdentity(
TIdentity identity)
{
var entityParameter = Expression.Parameter(typeof(TEntity));
var identityScope = Expression.MakeMemberAccess(
Expression.Constant(
new ExpressionScope<TIdentity>() { Value = identity }),
typeof(ExpressionScope<TIdentity>).GetProperty("Value"));
var equalityExpressions = this.MakeEqualityExpressions(
identityScope, entityParameter);
var aggregateEquality = equalityExpressions.Aggregate(
(x, y) => Expression.AndAlso(x, y));
var predicate = Expression.Lambda<Func<TEntity, bool>>(
aggregateEquality, entityParameter);
return predicate;
}
public IEnumerable<BinaryExpression> MakeEqualityExpressions(
MemberExpression identityScope, ParameterExpression entityParameter)
{
var mapExpression =
mappingEngine.CreateMapExpression<TIdentity, TEntity>();
var body = mapExpression.Body as MemberInitExpression;
var bindings = body.Bindings;
foreach (var binding in bindings.OfType<MemberAssignment>())
{
var memberExpression = binding.Expression as MemberExpression;
var left = Expression.Property(
identityScope, memberExpression.Member as PropertyInfo);
var right = Expression.Property(
entityParameter, binding.Member as PropertyInfo);
var equalityExpression = Expression.Equal(left, right);
yield return equalityExpression;
}
}
private class ExpressionScope<TDto>
{
public TDto Value { get; set; }
}
}