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.
Related
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 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.
C# Entity framework 4.0
I have a database with 10's of table with 2 common columns 'id' and 'modstamp'
to access modstamp in a table I have a function
protected internal override string GetModStampinChild(int sid)
{
DBContext sq = new DBContext();
return sq.xxxx.Where(s => s.id == sid)
.Select(s => s.modstamp).SingleOrDefault().ToModStampString();
}
where xxxx change for every table.
I am presently overriding this function for every table.
Is there a way to use some kind of generic "class" which I could use where "xxxx" would be any table?
First, you would need to have all of your Entities implement either an interface or an abstract class that contains both the ID and ModStamp properties in it, let's call it Stampable:
public abstract class Stampable
{
[Key]
public int ID { get; set; }
[Required]
public string ModStamp { get; set; }
}
At that point, all you need to do for your method is to have it implement generic typing:
protected internal override string GetModStampInChild<T>(int sid) where T : Stampable
{
using (var sq = new DbContext())
{
return sq.Set<T>.Where(s => s.id == sid)
.Select(s => s.modstamp)
.SingleOrDefault()
.ToModStampString();
}
}
If I understand you correctly, you need a property Set<T> of DbContext class:
First, create base class of all your entity classes with id and modstamp properties. Then:
protected internal override string GetModStampInChild<T>(int sid) where T : BaseEntity
{
using (var sq = new DbContext())
{
return sq.Set<T>.Where(s => s.id == sid)
.Select(s => s.modstamp)
.SingleOrDefault()
.ToModStampString();
}
}
But you must use code-first paradigm for this method.
Another option would be add a new Property to your entity class via the partial class feature of c#.
So the entity definition generated might look like this, note I have no idea what the actual DataType of your ModStamp column is:
public partial class Company
{
public int Id { get; set; }
public byte[] ModStamp { get; set; }
public string Name { get; set; }
public string City { get; set; }
public string State { get; set; }
}
Note the ModStamp column that you want to convert.
Then add to the Partial.cs file that EF creates code like this, note I have no idea what you actually want to do with the ModStamp value:
public static class ModConverter
{
public static string ToModStampString(byte[] modStamp)
{
return BitConverter.ToString(modStamp);
}
}
public partial class Company
{
public string ModStampString
{
get
{
return ModConverter.ToModStampString(this.ModStamp);
}
}
}
You would then have to manually add a new ModStampString Get Property for every Entity with a ModStamp Column like I did for the Company Entity.
Here is a solution that uses the Set method on the DbContext and expression trees to dynamically query that object.
private Expression<Func<TArg, bool>> CreatePredicate<TArg, TPredicateField>(string fieldName, TPredicateField value)
{
ParameterExpression parameter = Expression.Parameter(typeof(TArg), "o");
MemberExpression memberExpression = Expression.Property(parameter, fieldName);
var condition = Expression.Equal(memberExpression, Expression.Constant(value));
var lambda = Expression.Lambda<Func<TArg, bool>>(condition, parameter);
return lambda;
}
private Expression<Func<TArg, TPredicateField>> CreateSelector<TArg, TPredicateField>(string fieldName)
{
ParameterExpression parameter = Expression.Parameter(typeof(TArg), "o");
Expression propertyExpr = Expression.Property(parameter, fieldName);
var lambda = Expression.Lambda<Func<TArg, TPredicateField>>(propertyExpr, parameter);
return lambda;
}
public TSelectorField GetModStamp<TEntity, TPredicateField, TSelectorField>(TPredicateField id) where TEntity : class
{
using (var ctx = new OnTheFlyEntities("Data Source=(local);Initial Catalog=AscensionBO;Integrated Security=True;MultipleActiveResultSets=True"))
{
var predicate = CreatePredicate<TEntity, TPredicateField>("Id", id);
var selector = CreateSelector<TEntity, TSelectorField>("ModStamp");
TSelectorField item = ctx.Set<TEntity>().Where(predicate).Select(selector).SingleOrDefault();
return item;
}
}
You can then call it like this:
GetModStamp<Entity2, int, string>(1)
If you were willing to just return the found entity, you could eliminate the TSelectorField and then grab the ModStamp from the item after it is retrieved. That will drop one of the expression tree methods and a generic input on the main method.
As someone else suggested, you could go the interface route and use that example, it will be much simpler.
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; }
}
}
Let's say I have the following class :
public class Person {
public string FirstName { get; set; }
public string SurName { get; set; }
public int Age { get; set; }
public string Gender { get; set; }
}
Also, I have the following method and I am reaching out to person data via a repository.
public IEnumerable<Person> getPeople(string searchField, string searchTerm) {
//_repo.GetAll() returns IEnumerable<Person>
var model = _repo.GetAll();
//Need the logic here for filtering
return model;
}
As you can see I am getting two parameter for the method : searchField and searchTerm.
searchField is for the field name whose value will be used for filtering. searchTerm is the value which will be used to compare with retrived value (sorry if I am not clear here but this is the most I can come up with)
What I would normally do is as follows :
public IEnumerable<Person> getPeople(string searchField, string searchTerm) {
//_repo.GetAll() returns IEnumerable<Person>
var model = _repo.GetAll();
switch(searchField) {
case "FirstName":
model = model.Where(x => x.FirstName == searchTerm);
break;
case "SurName":
model = model.Where(x => x.SurName == searchTerm);
break;
//Keeps going
}
return model;
}
Which will work just fine. But if I make a change on my class, this code will have a change to break or be in lack of some functions if I add new properties this class.
What I am looking for is something like below :
NOTE :
This below code completely belongs to my imagination and there is no such a
thing exists.
model = model.Where(x => x.GetPropertyByName(searchField) == searchTerm);
Am I flying too high here if it is impossible or being complete idiot if there is already a built in way for this?
Looks like you need Dynamic Linq queries:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
I use this extension method to achieve what you want.
public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> source, string propertyName, string value)
{
Expression<Func<TEntity, bool>> whereExpression = x => x.GetType().InvokeMember(propertyName, BindingFlags.GetProperty, null, x, null).ObjectToString().IndexOf(value, StringComparison.InvariantCultureIgnoreCase) >= 0;
return source.Where(whereExpression);
}
Note: ObjectToString is just another extension method that returns string.Empty if the Object passed in is NULL
For linq2Object You can use reflection as bellow(it's not very fast):
model.Where(x => x.GetType().GetProperty(propName).GetValue(x, null) == propVal);
but for linq2Entity I think this doesn't work, it works for linq2objects.
I think the following implementation looks an awful lot like what you originally intended, although changing this to a generic method likely makes more sense.
public IEnumerable<Person> getPeople(string searchField, string searchTerm) {
PropertyInfo getter=typeof(Person).GetProperty(searchField);
if(getter==null) {
throw new ArgumentOutOfRangeException("searchField");
}
return _repo.GetAll().Where(x => getter.GetValue(x, null).ToString()==searchTerm);
}
This should be type-safe:
public IEnumerable<T> Where<T,U>(Func<T,U> propertySelector, U value)
{
return model.Where(x => propertySelector(x) == value);
}
usage:
Where((MyClass x) => x.PropertyName, propertyValue);
Or:
public IEnumerable<T> Where<T>(Func<T,bool> entitySelector)
{
return model.Where(entitySelector);
}
usage:
Where<MyClass>(x => x.PropertyName == propertyValue && x.OtherProperty == otherValue);
Use Reflection
model = model.Where(x =>
((string)x.GetType().GetProperty("searchField").GetValue(0, null)) == searchTerm);
Rather than messing with reflection, custom expression trees, etc., when using Entity Framework, consider using the Builder Method extensions to the standard LINQ operators which take strings rather than lambdas. These are much easier to build for dynamic query requirements:
string filter = String.Format("it.{0} = #value", fieldName);
var model = context.People.Where(filter, new ObjectParameter("value", searchValue));
Of course, this would mean that you yould need to modify your repository to return IObjectSet rather than IEnumerable. It would perform better as well. By returning IEnumerable, you are hydrating every row in your database to your repository and then filtering via LINQ to Objects rather than applying the filter back in your database.
For more information about the Builder Methods in EF, see the BuilderMethodSamples.cs in http://archive.msdn.microsoft.com/EFQuerySamples/Release/ProjectReleases.aspx?ReleaseId=4422.