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.
Related
So i have this class:
public class Person
{
public string Name { get; set; }
public string Id{ get; set; }
}
And i have this Collection:
List<Person> persons = new List<Person>();
So so example if i want Person with specific Name:
public Person Search(string property )
{
return persons.Find(x => x.Name == property);
}
So now i am wonder how to write generic function that search for specific Person object base on some property and this function will also need to support future Property that at this time does not exist but if it will add to my class Person it will also works.
Actually the Find-method already does this, there´s no need to create your own method for you, unless you don´t want to expose your list to the outside of your class.
In this case make your Search-method expect a predicate:
public Person Search(Predicate<Person> predicate)
{
return persons.Find(predicate);
}
Now you may easily use this like this:
Person p = myClass.Search(x => x.Name == "Me");
or
Person p = myClass.Search(x => x.Age > 40);
In case you have multiple objects that fit your condition you may either use List<T>.FindAll(predicate) or IEnumerable<T>.Where(predicate). For the latter however you´d have to use a Func<Person, bool> instead of a Predicate<Person>:
public IEnumerable<Person> Search(Func<Person, bool> predicate)
{
return persons.Where(predicate);
}
which can be used exactly the same way as the above examples (except that it returns a collection of items instead a single one).
public T Search<T>(IEnumerable<T> source, Func<T, bool> filter)
{
return source.FirstOrDefault(filter);
}
Usage:
var erofh = Search(people, q => q.Name == "Erofh Tor");
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 have a Class with the following:
public class TestClass {
string Account1 {get;set;}
string Account2 {get;set;}
string Account3 {get;set;}
}
What I would like is to be able to have a method that is similar to the following:
public TestClass[] GetTestClass(string value, string AccountName)
where i can pass in a value say "John" and the AccountName would be "Account1"
and it will go through a list of TestClass and return an array or list of TestClass objects where there exists a value "John" in the Property "Account1"
Is there a better method of doing this or any thoughts would help.
Note: This is a model based of a SQL Table
You can use the reflection to get what you want, Your method will look like this,
public List<TestClass> GetTestClass(string value, string AccountName)
{
foreach(TestClass test in yourListOfTestClass)
{
if (test.GetType().GetProperty(AccountName).GetValue(test, null).Equals(value))
listToReturn.Add(test);
}
return listToReturn
}
Note - Code is not tested. Might have synatx error.
You can send in a method for accessing the property instead of the property name:
public TestClass[] GetTestClass(string value, Func<TestClass, string> getAccountName) {
return accounts.Where(x => getAccountName(x) == value).ToArray();
}
Usage:
TestClass[] johnsAccounts = GetTestClass("John", a => a.Account1);
Or simply use it directly:
TestClass[] johnsAccounts = accounts.Where(a => a.Account1 == "John").ToArray();
Is there a better method of doing this or any thoughts would help.
You can use List of strings instead of multiple string variables as you are returning array from GetTestClass. It will keep it simple as well.
public class TestClass
{
List<string> Accounts = new List<string>();
}
Now you will only pass the value to method. Using the LinQ will filter out the desired string list using Where.
public List<string> GetTestClass(string value)
{
return Accounts.Where(account => account == value).ToList();
}
Edit The OP want two things AccountName and the Name, this would require to have a account class instead of string.
class Account
{
string AccountName { get; set; }
string Name { get; set; }
}
public class TestClass
{
List<Account> Accounts = new List<Account>();
}
public Account GetTestClass(Account account)
{
return Accounts.Where(account => a.AccountName == account.AccountName && a.Name == account.Name).FirstOrDefault();
}
Yes you can do this.
Take a look into Reflection. This should get you started.
Example:
public TestClass[] GetTestClass(string value, string AccountName)
{
var propertyInfo = typeof(TestClass).GetProperty(AccountName);
var list = new List<TestClass>();
foreach(var tc in [YOUR_OBJECTS])
{
if(propertyInfo.GetValue(tc, null) == value)
{
list.add(tc);
}
}
return list.ToArray();
}
Well, you can achieve that using Reflection. Though, I don't think I would do that this way, since there are other ways to implement what you want differently.
A better way in my opinion would be to create a type-safe enum that would do that. In the members of the type-safe enum, you could specify an action that would run against a 'TestClass' object.
You would be able to call 'GetTestClass' in this way:
var accounts1 = GetTestClass(testClassesCollection, "john", TestProperties.Account1);
var accounts2 = GetTestClass(testClassesCollection, "john", TestProperties.Account2);
var accounts3 = GetTestClass(testClassesCollection, "john", TestProperties.Account3);
Where 'testClassesColleciton' is the collection of 'TestClass' that you have. You could remove this argument if the method is an object member.
The type-safe enum is implemented this way:
public sealed class TestProperties
{
public static readonly TestProperties Account1 = new TestProperties((t, name) => t.Account1 == name);
public static readonly TestProperties Account2 = new TestProperties((t, name) => t.Account2 == name);
public static readonly TestProperties Account3 = new TestProperties((t, name) => t.Account3 == name);
private Func<TestClass, string, bool> _checkFunc;
private TestProperties(Func<TestClass, string, bool> func)
{
_checkFunc = func;
}
public bool IsApplicable(TestClass test, string name)
{
return _checkFunc(test, name);
}
}
Then, you can implement the GetTestClass this way:
public TestClass[] GetTestClass(IEnumerable<TestClass> testClasses, string value, TestProperties property)
{
return testClasses.Where( t => property.IsApplicable(t)).ToArray();
}
I wouldn't use reflection because of it's performance overhead and it's maintenance danger. With a plain simple reflection, we will pass the property's name as a string parameter. What would happen if somebody has changed the property's name in the class? Even with Visual Studio refactoring (ctrl + R, R), the string parameter value will not be updated. Since the code will compile normally, the bug will be discovered only at run-time.
I agree with #Rouby that a plain simple reflection will be faster to develop than the type-safe enum way that I have suggested. Though, personally, I don't think that it will be that much cheaper (development-cost wise) and I also think that the potentially dangerous scenario that I talked about earlier (regarding refactoring) would have a bigger development-cost penalty, specially, when it is a legacy/old code.
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; }
}
}