Make Linq function more generic parameters - c#

How do I generalize this function? I want Name property to be variable, and have function accept, replace the persons class with any class.
// Filtering logic
Func<SampleFilterModel, IEnumerable<Person>> filterData = (filterModel) =>
{
return persons.Where(p => p.Name.StartsWith(filterModel.Term ?? String.Empty, StringComparison.InvariantCultureIgnoreCase))
.Skip((filterModel.Page-1) * filter.Limit)
.Take(filterModel.Limit);
};
Other items:
IEnumerable<Person> persons = new List<Person>() {
new Person() { Name = "Laura Callahan", DOB = DateTime.Parse("1958-01-09"), Email = "laura.callahan#test.com" },
new Person() { Name = "Anne Dodsworth", DOB = DateTime.Parse("1966-01-27"), Email = "anne.dodsworth#test.com" }
};
public class SampleFilterModel
{
public int Page { get; set; }
public int Limit { get; set; }
public string Term { get; set; }
public SampleFilterModel()
{
this.Page = 1;
this.Limit = 3;
}
public object Clone()
{
var jsonString = JsonConvert.SerializeObject(this);
return JsonConvert.DeserializeObject(jsonString, this.GetType());
}
}
Current attempt, trying to modify/rework as needed:
giving some problems/errors:
Do I have to declare all variables as static?
Getting error message:
'T' does not contain a definition for 'propertyInfo' and no accessible extension method 'propertyInfo' accepting a first argument of type 'T' could be found (are you missing a using directive or an assembly reference?)
Code:
public class Filterclass<T> where T : class
{
public static string ColumnName;
public static SampleFilterModel filter = new SampleFilterModel();
public static IEnumerable<T> input;
public Func<SampleFilterModel, IEnumerable<T>> filterData = (filterModel) =>
{
var propertyInfo = input.GetType().GetProperty(ColumnName);
return input.Where(p => p.propertyInfo.StartsWith(filterModel.Term ?? String.Empty, StringComparison.InvariantCultureIgnoreCase))
.Skip((filterModel.Page - 1) * filter.Limit)
.Take(filterModel.Limit);
};
}

While the rest of the question does not make much sense to me, the part about trying to make a generic function would require building up a predicate for the Where call
Review the comments included to get an example of how to build up the lambda expression used for the predicate
public static class Filterclass {
static readonly MethodInfo startsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string), typeof(System.StringComparison) });
public static IEnumerable<T> FilterData<T>(this IEnumerable<T> input, string columnName, FilterModel filterModel) where T : class {
var type = typeof(T);
var propertyInfo = type.GetProperty(columnName);
//T p =>
var parameter = Expression.Parameter(type, "p");
//T p => p.ColumnName
var name = Expression.Property(parameter, propertyInfo);
// filterModel.Term ?? String.Empty
var term = Expression.Constant(filterModel.Term ?? String.Empty);
//StringComparison.InvariantCultureIgnoreCase
var comparison = Expression.Constant(StringComparison.InvariantCultureIgnoreCase);
//T p => p.ColumnName.StartsWith(filterModel.Term ?? String.Empty, StringComparison.InvariantCultureIgnoreCase)
var methodCall = Expression.Call(name, startsWith, term, comparison);
var lambda = Expression.Lambda<Func<T, bool>>(methodCall, parameter);
return input.Where(lambda.Compile())
.Skip((filterModel.Page - 1) * filterModel.Limit)
.Take(filterModel.Limit);
}
}
Now this assumes that the column is of type string. Any other type would cause this to throw an exception.
You would also need to assume that p is not null else again a null reference exception will be thrown when trying to call instance members on a null reference.
Sample unit test of the extension method in use
[TestClass]
public class MyTestClass2 {
[TestMethod]
public void MyTestMethod() {
//Arrange
IEnumerable<Person> persons = new List<Person>() {
new Person() { Name = "Nancy Davolio", DOB = DateTime.Parse("1948-12-08"), Email = "nancy.davolio#test.com" },
new Person() { Name = "Andrew Fuller", DOB = DateTime.Parse("1952-02-19"), Email = "andrew.fuller#test.com" },
new Person() { Name = "Janet Leverling", DOB = DateTime.Parse("1963-08-30"), Email = "janet.leverling#test.com" },
new Person() { Name = "Margaret Peacock", DOB = DateTime.Parse("1937-09-19"), Email = "margaret.peacock#test.com" },
new Person() { Name = "Steven Buchanan", DOB = DateTime.Parse("1955-03-04"), Email = "steven.buchanan#test.com" },
new Person() { Name = "Michael Suyama", DOB = DateTime.Parse("1963-07-02"), Email = "michael.suyama#test.com" },
new Person() { Name = "Robert King", DOB = DateTime.Parse("1960-05-29"), Email = "robert.king#test.com" },
new Person() { Name = "Laura Callahan", DOB = DateTime.Parse("1958-01-09"), Email = "laura.callahan#test.com" },
new Person() { Name = "Anne Dodsworth", DOB = DateTime.Parse("1966-01-27"), Email = "anne.dodsworth#test.com" }
};
var filter = new FilterModel {
Term = "Nancy"
};
//Act
var data = persons.FilterData("Name", filter);
//Assert
data.Should().NotBeEmpty();
}
}

Related

Set PredicateBuilder also on child collection

I am trying to apply the predicate not only to the entity parent but also to the child collection. The following is part of my code:
var predicate = PredicateBuilder.New<Entity>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or(p => p.Name.Equals(temp));
predicate = predicate.Or(p => p.Addresses.Select(x=> x.Name.Equals(temp));
}
The line predicate = predicate.Or(p => p.Addresses.Select(x=> x.Name.Equals(temp)); is not working ? any ideas as to why?
EDIT
In the following demo I am asking to get entity parent and child with name = tom but I also get child name = tom2.
private static void Main(string[] args)
{
var result = GetAll(GetAll(), new List<string> { "tom" });
}
private static List<User> GetAll()
{
return new List<User>
{
new User
{
Id = 1,
Name = "tom",
Addesses = new List<Addesses>
{
new Addesses { Id = 1, Name = "tom" },
new Addesses { Id = 1, Name = "tom2" },
}
},
new User
{
Id = 1,
Name = "sam",
Addesses = new List<Addesses>
{
new Addesses { Id = 1, Name = "sam" },
new Addesses { Id = 1, Name = "sam2" },
}
},
};
}
private static List<User> GetAll(List<User> users, List<string> keywords)
{
var predicate = PredicateBuilder.New<User>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or(p => p.Name.Equals(temp));
predicate = predicate.Or(p => p.Addesses.Any(x => x.Name.Equals(temp)));
}
var result = users
.Where(predicate)
.ToList();
return result;
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public List<Addesses> Addesses { get; set; }
}
public class Addesses
{
public int Id { get; set; }
public string Name { get; set; }
}
The call to p.Addresses.Select(x=> x.Name.Equals(temp) is not returning a boolean result.
Depending on your actual logic you may want to look into Any:
predicate = predicate.Or(p => p.Addresses.Any(x=> x.Name.Equals(temp));
or All:
predicate = predicate.Or(p => p.Addresses.All(x=> x.Name.Equals(temp));

Using Dictionary Parameters as Optional Arguments when Calling Method

I'm presently trying to use a dictionary values to name optional parameters when invoking a method. I'm not sure this is possible with c# but I do something similar with queries using dynamic SQL.
string[] dobArrayKey = {"dob: "};
string[] dobArrayValue = {txtDob.Text};
string[] ptntNumArrayKey = { "PatientID: " };
string[] ptntNumArrayValue = { txtOfficeMR.Text};
string[] nameArrayKey = { "FirstName: ", "LastName: " };
string[] nameArrayValue = { txtFirstname.Text, txtLastname.Text };
List<List<string>> searchResults = new List<List<string>>();
Dictionary<string[], string[]> searchCriteria = new Dictionary<string[], string[]>
{
{dobArrayKey,dobArrayValue}
,{ptntNumArrayKey,ptntNumArrayValue}
,{nameArrayKey,nameArrayValue}
};
foreach (var item in searchCriteria)
{
if (item.Value[0] != "" && item.Value[0] != null)
{
searchResults.Add(new List<string>());
for (int x = 0; x <= item.Key.Count(); x++)
{
string strJSON = doPatientSearch(Convert.ToInt32(au.UserID)
, Convert.ToInt32(Session["PracticeID"]), au.SessionID, item.Key[x].ToString() : item.Value[x].ToString() );
PatientSearchResponse ptLi = JsonConvert.DeserializeObject<PatientSearchResponse>(json2);
foreach (PatientList3 patient in ptLi.PatientList)
{
searchResults[x].Add(patient.PatientNumber);
}
}
}
}
public static string doPatientSearch(int UserID, int PracticeID, string SessionID, string PatientID = null,
,string first = null, string last = null, string dob = null, string social = null)
{
//search
}
My colleague suggested I change the method itself by removing all of the optional parameters and instead passing through a dictionary that contains all of the parameters and handling them inside the method.
I think that would work, but for curiosities sake I wanted to get some feedback and find out whether or not something like I'm attempting to do in the above code is possible.
If it is impossible but there is another way of achieving the desired outcome I'd love to see your suggestions.
Thank you in advance.
Pass an expression
Since the criteria are used post-hoc (i.e. by filtering a complete resultset), you can use LINQ to filter the results. For maximum flexibility, the caller can pass in an Expression to be used as a callback on each item to determine if it should be included.
To get a filtered resultset:
public IEnumerable<Patient> FindPatients(Func<Patient,bool> criteria)
{
return sourceData
.Where (criteria);
}
To return a single result:
public Patient FindPatient(Func<Patient,bool> criteria)
{
return sourceData
.Single(criteria);
}
The criteria expression is just a function that accepts a patient and returns a Boolean. The caller can write this any way desired, or insert it as a lambda expression.
var results = patients.FindPatients( p => p.LastName == "Doe" );
Or
var results = patients.FindPatients
(
p =>
p.LastName.Contains("Doe") &&
p.PracticeID == 12
);
Or
var singleResult = patients.FindPatient( p => p.UserID == 1);
As you can see, the caller can provide literally any criteria desired, and has the benefit of type safety and early binding. This is far superior to using a Dictionary which has neither.
Full example code:
class Patient
{
public int UserID { get; set; }
public int PracticeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DOB { get; set; }
public string Social { get; set; }
public override string ToString()
{
return string.Format("{0} {1} {2}", UserID, FirstName, LastName);
}
}
class PatientRepository
{
static private readonly List<Patient> sourceData = new List<Patient>
{
new Patient
{
UserID = 1, PracticeID = 10, FirstName = "John", LastName = "Doe", DOB = DateTime.Parse("1/2/1968"), Social="123456789"
},
new Patient
{
UserID = 2, PracticeID = 10, FirstName = "Jane", LastName = "Doe", DOB = DateTime.Parse("1/2/1958"), Social="123456790"
},
new Patient
{
UserID = 3, PracticeID = 10, FirstName = "John", LastName = "Carson", DOB = DateTime.Parse("4/1/1938"), Social="123456791"
}
};
public IEnumerable<Patient> FindPatients(Func<Patient,bool> criteria)
{
return sourceData
.Where (criteria);
}
public Patient FindPatient(Func<Patient,bool> criteria)
{
return sourceData
.Single(criteria);
}
}
public class Program
{
public static void Main()
{
//Get a reference to the data store
var patients = new PatientRepository();
Console.WriteLine("Multiple record search");
var results = patients.FindPatients
(
p => p.LastName == "Doe"
);
foreach (var p in results)
{
Console.WriteLine(p);
}
Console.WriteLine("Single record search");
var singleResult = patients.FindPatient
(
p => p.UserID == 1
);
Console.WriteLine(singleResult);
}
}
Output:
Multiple record search
1 John Doe
2 Jane Doe
Single record search
1 John Doe
See the working code on DotNetFiddle

Create Expression from a string containing a LINQ expression

I'm looking for a way to create an expression from a string containg a Linq expression itself.
Assume the following class:
public class Person
{
public string Name { get; set; }
public string FirstName { get; set; }
public Address[] Address { get; set; }
}
public class Address
{
public string City { get; set; }
}
A simple expression can be generated with the following code:
var expression = #"x.Name";
var p = Expression.Parameter(typeof(Person), "x");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, expression);
The sample below will return Name 1 as expected:
static void Main(string[] args)
{
var personsCollection = new List<Person>
{
new Person { Name = "Name 1", FirstName = "Firstname 1", Address = new Address { City = "City 1" } },
new Person { Name = "Name 2", FirstName = "Firstname 2", Address = new Address { City = "City 2" } }
};
var expression = #"x.Name";
var p = Expression.Parameter(typeof(Person), "x");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, expression);
var result = e.Compile().DynamicInvoke(personsCollection[0]);
}
Now, assume that I change the expression to project a List<T>.
The expression will than be: var expression = #"x.Address.Select(dest => new { MyCity = dest.City })";
Using this is the sample code above throws the following exception: "Unknown identifier 'dest'"
Any idea on how this can be solved?

Building a dynamic expression tree to filter on a collection property 2

Maybe this is a duplicate thread, but I am going to try, because there is a tiny difference.
I am trying to build a dynamic expression to filter a collection property.
The code:
public class TestEntity
{
public int ID { get; set; }
public string Name { get; set; }
public IEnumerable<string> Values { get; set; }
}
public class TestFilter
{
public TestFilter()
{
var itens = new List<TestEntity>();
itens.Add(new TestEntity { ID = 1, Name = "Test1", Values = new List<string> { "V1", "V2" } });
itens.Add(new TestEntity { ID = 2, Name = "Test2", Values = new List<string> { "V6", "V3" } });
itens.Add(new TestEntity { ID = 3, Name = "Test3", Values = new List<string> { "V4", "V5" } });
itens.Add(new TestEntity { ID = 4, Name = "Test4", Values = new List<string> { "V2", "V3" } });
itens = itens.Where(e => e.Values.Any(c => c.Equals("V2"))).ToList();
**//Result IDs: 1, 4**
}
}
The filter above will give me IDs 1 and 4 as result.
I want to filter entities where exists a certain value in the collection "Values".
So far, I have tried this thread, but didnt realize how it can be done.
Any help would be apreciated.
So you are seeking for a method which given a collection property name and value will produce Where predicate like e => e.Collection.Any(c => c == value).
You can use the following extension method (hope the code is self explanatory):
public static class QueryableExtensions
{
public static IQueryable<T> WhereAnyEquals<T>(this IQueryable<T> source, string collectionName, object value)
{
var e = Expression.Parameter(typeof(T), "e");
var collection = Expression.PropertyOrField(e, collectionName);
var itemType = (collection.Type.IsIEnumerableT() ? collection.Type :
collection.Type.GetInterfaces().Single(IsIEnumerableT))
.GetGenericArguments()[0];
var c = Expression.Parameter(itemType, "c");
var itemPredicate = Expression.Lambda(
Expression.Equal(c, Expression.Constant(value)),
c);
var callAny = Expression.Call(
typeof(Enumerable), "Any", new Type[] { itemType },
collection, itemPredicate);
var predicate = Expression.Lambda<Func<T, bool>>(callAny, e);
return source.Where(predicate);
}
private static bool IsIEnumerableT(this Type type)
{
return type.IsInterface && type.IsConstructedGenericType &&
type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
}
}
like this:
itens = itens.AsQueryable().WhereAnyEquals("Values", "V2").ToList();
If you step through the code, the variable predicate contains the expression you are asking for.

Get requested column distinct values from list

I have a list with multiple columns. I want to filter the list based on the requested column name (column name will come as a parameter) with distinct values.
IList<obj1> objTemp= new List<obj1>();
for (int i = 0; i < 15; i++)
{
obj1 temp= new obj1();
temp.Name = "Name" + i;
temp.Age= "Age" + i;
temp.Company= "Company" + i;
objTemp.Add(temp);
}
var distinctTypeIDs = objTemp.Select(x => x.**{my requested column}**).Distinct();
You can use reflection for getting desired property by it's name:
var distinctTypeIDs = objTemp.Select(x => x.GetType().GetProperty("requested_column").GetValue(x))
.Distinct();
I've always been a fan of "mapping" a column to an anonymous method responsible for retrieving the contents of that column:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
var items = new List<SomeObject> {new SomeObject { Age = 10, Name = "Daniel", Company = "InCycle" },
{new SomeObject { Age = 20, Name = "Not Daniel", Company = "Not InCycle" }
}};
var result = Filter<int>(items, "Age");
Console.WriteLine(result.Last());
}
public static IEnumerable<T> Filter<T>(IEnumerable<SomeObject> items, string filterCriteria)
{
var mappings = new Dictionary<string, Func<IEnumerable<SomeObject>, IEnumerable<T>>>
{
{ "Age", filterItems => filterItems.Select(item => item.Age).Distinct().Cast<T>() },
{ "Name", filterItems => filterItems.Select(item => item.Name).Distinct().Cast<T>() },
{ "Company", filterItems => filterItems.Select(item => item.Company).Distinct().Cast<T>() }
};
return mappings[filterCriteria](items);
}
}
public class SomeObject
{
public int Age {get;set;}
public string Name {get;set;}
public string Company {get; set;}
}
The downside to this approach is that if you add additional properties, you could forget to add them to the filtering. Expressions are a solid approach as well.
One way is to use a method like this.
private IList<obj1> SortListAccordingToParameter(string filter)
{
if(filter == "Name")
return objTemp.Select(x => x.Name).Distinct();
else if(filter == "Age")
return objTemp.Select(x => x.Age).Distinct();
else if(filter == "Company")
return objTemp.Select(x => x.Company).Distinct();
}
If you know the type of the property you will be searching for, you could use expressions.
string propName = "Age";
var paramExpression = Expression.Parameter(typeof(Obj1));
// o =>
var memberExpression = Expression.Property(paramExpression, propName);
// o => o.Age
var lambdaExpression = Expression.Lambda<Func<Obj1, string>>(memberExpression, paramExpression);
// (o => o.Age)
var compiled = lambdaExpression.Compile();
IList<Obj1> objTemp = new List<Obj1>();
for (var i = 0; i < 15; i++) {
Obj1 temp = new Obj1();
temp.Name = "Name" + i;
temp.Age = "Age" + i;
temp.Company = "Company" + i;
objTemp.Add(temp);
}
var results = objTemp.Select(compiled);
// equivalent to objTemp.Select(o => o.Age), plus a delegate call and the time to
// compile the lambda.
I would probably wrap this up in a static class, like this:
static class Gen<TModel, TProp> {
public static Func<TModel, TProp> SelectorExpr(string propertyName) {
var pExpr = Expression.Parameter(typeof (TModel));
var mExpr = Expression.Property(pExpr, propertyName);
var lExpr = Expression.Lambda<Func<TModel, TProp>>(mExpr, pExpr);
return lExpr.Compile();
}
}
that way you can write your selector like:
var results = objTemp.Select(Gen<Obj1, string>.SelectorExpr(propName));
That seems a bit more clear to me what it is I'm doing, especially if I'm reading expression DOM code I wrote 6 months after.
public class Test
{
public string name { get; set; }
public string age { get; set; }
public string contact { get; set; }
public Test getName(string name)
{
List<Test> testList = new List<Test>();
testList.Add(new Test { name = "Developer", age = "24", contact = "99009900990" });
testList.Add(new Test { name = "Tester", age = "30", contact = "009900990099" });
return testList.Where(c => c.name == name).FirstOrDefault();
}
}
static void Main(string[] args)
{
Test testObj = new Test();
Test selectedObj = testObj.getName("Developer");
}

Categories

Resources