I want to write an extension method to filter all the people with towns in a people object with the towns in shop object
class people
string name
string town
class shops
string category
string town
i know i can write
var x = from p in people
from s in shops
where p.town == s.town
but i would like to know how to write
var x = from p in people.FilterByTown(p) or FilterByTown(p => p.town) or however it is!!
where FilterByTown is the extension method and all the magic works in there and the object i pass in gets compared with the shop object.
It needs to work with different objects being fed to the method
Hope that all makes sense, the code above is obviously pseudo!
Using reflection, you can filter based on any property of any type:
public static IEnumerable<T> FilterByProperty<T>(this IEnumerable<T> source,
string property,
object value)
{
var propertyInfo = typeof(T).GetProperty(property);
return source.Where(p => propertyInfo.GetValue(p, null) == value);
}
Usage:
IEnumerable<People> cityPeople = myPeople.FilterByTown("Town", "MyCity");
And if you want a list:
List<People> cityPeopleList = myPeople.FilterByTown("MyCity").ToList();
Assuming you have 2 collections people and shops you can write this:
List<People> people = ...
List<Shops> shops = ...
IEnumerable<People> Filter(this IEnumerable<People> people, IEnumerable<Shops> shops){
var result = people.Where(p=>shops.Any(s=>s.town == p.town));
return result;
}
If you want to sort all classes by some arbitrary property you can try this version:
public static IEnumerable<T1> Filter<T1, T2>(
this IEnumerable<T1> one,
IEnumerable<T2> two, string property)
{
var result = one.Where(o => two.Any(t =>
o.GetType().GetProperty(property).
GetValue(o, null).Equals(t.GetType().
GetProperty(property).GetValue(t, null))));
return result;
}
Of course you need to be sure that property is valid and both objects have it.
If I understand your question correctly you want something like this:
public static IEnumerable<People> FilterByTown(this IEnumerable<People> people, IList<Shop> shops)
{
return people.Where(p => shops.Any(s => s.Town == p.Town));
}
Usage:
peoples.FilterByTown(shops);
You can speed up the queries if you create a list of unique towns
public static class PeopleExtensions
{
private static List<string> _distinctShopTowns;
private static List<Shop> _shops;
public static List<Shop> Shops
{
get { return _shops; }
set {
_shops = value;
_distinctShopTowns = _shops
.Select(shop => shop.town)
.Distinct()
.ToList();
}
}
public static IEnumerable<Person> PeopleInTownsWithShops(this IEnumerable<Person> people)
{
return people.Where(p => _distinctShopTowns.Contains(p.town));
}
}
You can call it like this
List<Shop> shops = ...;
List<Person> people = ...;
PeopleExtensions.Shops = shops; // Do every time the shop list changes.
var x = from p in people.PeopleInTownsWithShops();
Related
I have a sequential filter on an IQueryable<Person> using an instance of my Person class, running like so:
public IQueryable<Person> FilterPeopleOnPersonCriteria(IQueryable<Person> people, Person p)
{
if (!string.IsNullOrEmpty(p.FirstName))
{
people = people.Where(x => x.FirstName == p.FirstName);
}
if (!string.IsNullOrEmpty(p.Surname))
{
people = people.Where(x => x.Surname == p.Surname);
}
// etc. for all Person's string properties
return people;
}
I'm figuring I can DRY myself off and replace the repetitions of if/else if I could pass the people, the Person and which property I'd like to apply the filter to, to a specific filter method, something like this:
public IQueryable<Person> FilterPeopleOnProperty(IQueryable<Person> people, Person p, Person.Property personProperty)
{
if (!string.IsNullOrEmpty(p.personProperty))
{
people = people.Where(x => x.personProperty == p.personProperty);
}
return people;
}
SO in the end my original query would just look like:
public IQueryable<Person> FilterPeopleOnPersonCriteria(IQueryable<Person> people, Person p)
{
people = FilterPeopleOnPersonProperty(people, p, new Person().FirstName);
people = FilterPeopleOnPersonProperty(people, p, new Person().Surname);
// etc. for all properties on p
return people;
}
I'm assuming this would be possible using some Generic Types and new instances of my Person class but have no real idea where to start.
I guess that that's what you're looking for:
namespace Extensions
{
using System;
using System.Linq;
using System.Collections.Generic;
public static class IEnumerableExtensions
{
public static IEnumerable<T> WhereIf<T>(this IEnumerable<T> collection, bool condition, Func<T, bool> predicate)
{
return condition ? collection.Where(predicate) : collection;
}
}
}
We simply do the filtration only if the condition was true. I used IEnumerable<T> since that's more generic and is implemented by IQueryable<T> anyway, but you could still use IQueryable<T> instead. If you want to use IQueryable<T> instead, you're gonna need to use System.Linq.Expressions, and replace Func<T, bool> with Expression<Func<T, bool>>.
A use-case similar to yours would look like this:
public IEnumerable<Person> FilterPeople(IEnumerable<Person> people, Person person)
{
return people.WhereIf(!string.IsNullOrEmpty(person.FullName), p => p.FullName == person.FullName);
}
If I understood what you are asking right, you want to pass a Person object where some (string) properties are filled like "Starts*", "*ends", "ExactValue" and you use that Person as a base for QBE (Query By Example. ie:
Person p = new Person {FirstName="S*", LastName="*doe", Country="USA"};
Would do a search like:
people.Where(p => p.FirstName.StartsWith("S") &&
p.LastName.EndsWith("doe") &&
p.Country == "USA");
right? If so, then I think you could utilize Scott Gu's dynamic Linq which some good people made available on NuGet and documented here.
Here is some sample code for your case using that Nuget lib (System.Linq.Dynamic):
public IQueryable<Person> FilterPeopleOnPersonCriteria(IQueryable<Person> people, Person p)
{
var t = p.GetType();
var fields = t
.GetFields()
.Where(x => x.FieldType == typeof(string) &&
!string.IsNullOrEmpty((string)t.GetField(x.Name).GetValue(p)))
.Select(x => x.Name);
foreach (var f in fields)
{
var fi = t.GetField(f);
string pVal = (string)fi.GetValue(p);
if (pVal.Contains('*'))
{
var query = $"{f}.{(pVal.StartsWith("*") ? "Ends" : "Starts")}With(#0)";
people = people.Where(query, pVal.Replace("*", ""));
}
else
{
var query = $"{f} == #0";
people = people.Where(query, pVal);
}
}
return people;
}
You would call it like:
var personQBE = new Person {
FirstName="J*",
LastName="Doe"
});
var result = FilterPeopleOnPersonCriteria(people, personQBE);
I want to filter an IEnumerable object by a specific property of whatever object it is collecting. I want the option to filter by one or more property value but how many values (and what values) to filter by is only known at runtime.
Ok, so to give an example, the collected objects could be the following struct:
public struct Person
{
public string Name { get; set; }
public string Profession{ get; set; }
}
This struct could then be used by the following list, which I have populated with some arbitrary values:
List<Person> people= new List<Person>;
people.Add(new Person(){Name = "Mickey", Profession="Tinker"};
people.Add(new Person(){Name = "Donald", Profession="Tailor"};
people.Add(new Person(){Name = "Goofy", Profession="Soldier"};
people.Add(new Person(){Name = "Pluto", Profession="Spy"};
This is then put into an IEnumerable (all of them are transferred to it first)
var wantedPeople = from n in this.people select n;
So say a user was only interested in the "Tailor" and "Spy" professions, and via some sort of gui trickery the following collection was created:
List<string> wantedProfessions = new List<string>();
wantedProfessions.Add("Tailor");
wantedProfessions.Add("Spy");
Now what Linq statement can I use to filer my wantedPeople so it only includes the tailor and spy entries?
I know I could use a where clause but I don't know how to tailor it to get what I want (and doing the following is not what I want as it only works with the wantedProfessions collection above (e.g. this collection will change at runtime):
wantedPeople = from n in wantedPeople
where n.Profession == wantedProffessions[0] || n.Profession == wantedProffessions[1]
select n;
If you want to check any wanted profession from given list:
wantedPeople = from n in wantedPeople
where wantedProffessions.Contains(n.Profession)
select n;
Or you can build query with lambda syntax by applying filters one by one:
var query = people.AsEnumerable();
if (!String.IsNullOrEmpty(name))
query = query.Where(p => p.Name == name);
if (wantedProfessions.Any())
query = query.Where(p => wantedProfessions.Contains(p.Profession));
If you wanted to create more complex filters, like some name, and several professions, you can use Specification pattern. Specification can be defined by this simple interface:
public interface ISpecification<T>
{
bool Satisfied(T entity);
}
It just checks whether given entity (person) satisfies specification. Specification also look very simple:
public class PersonNameSpecification : ISpecification<Person>
{
private string _name;
public PersonNameSpecification(string name)
{
_name = name;
}
public bool Satisfied(Person person)
{
return person.Name == _name;
}
}
Profession specification:
public class PersonProfessionSpecification : ISpecification<Person>
{
private string[] _professions;
public PersonProfessionSpecification(params string[] professions)
{
_professions = professions;
}
public bool Satisfied(Person person)
{
return _professions.Contains(person.Profession);
}
}
You can create specifications which implement boolean logic, like OrSpecification or AndSpecification:
public class AndSpecification<T> : ISpecification<T>
{
private ISpecification<T> _specA;
private ISpecification<T> _specB;
public AndSpecification(ISpecification<T> specA, ISpecification<T> specB)
{
_specA = specA;
_specB = specB;
}
public bool Satisfied(T entity)
{
return _specA.Satisfied(entity) && _specB.Satisfied(entity);
}
}
public static class SpecificationExtensions
{
public static ISpecification<T> And<T>(
this ISpecification<T> specA, ISpecification<T> specB)
{
return new AndSpecification<T>(specA, specB);
}
}
Now you can create complex specification which describes people you want to get:
var professionSpec = new PersonProfessionSpecification("Tailor", "Spy");
var nameSpec = new PersonNameSpecification("Pluto");
var spec = professionSpec.And(nameSpec);
And get required people:
var result = people.Where(spec.Satisfied);
Sergey B's Solution is the correct one for your example.
Assuming that you weren't using a collection that had the Contains() method, you could also do the following:
var wantedPeople = from n in people
from p in wantedProffessions
where n.Profession.Equals(p)
select n;
Is there a way I can make the following db query builder generic?
private IQueryable<Foo> ByName(IQueryable<Foo> dbQuery, Query query)
{
string[] searchTerms = query.Data.Replace(" ","").ToLower().Split(',');
if (query.Exclude)
{
return dbQuery.Where(x => searchTerms.All(
y => y != x.Name.Replace(" ", "").ToLower()));
}
return dbQuery.Where(x => searchTerms.Any(
y => y == x.Name.Replace(" ", "").ToLower()));
}
I've got the same function for lots of different properties of Foo. ByCounty, ByTown, ByStreet etc etc.
I've written some functions that return linq before like the following
public Expression<Func<Foo, bool>> FoosAreWithinDistanceFromGeocode(
double distance, Geocode geocode)
{
double distanceSquare = distance * distance;
return foo => ( SqlFunctions.Square((double)(
foo.Address.Geocode.Easting - geocode.Easting)) +
SqlFunctions.Square((double)(fooAddress.Geocode.Northing -
geocode.Northing)) ) <= distanceSquare;
}
But I can't seem to find if the Linq-to-SQL stuff can't use generics or if it's possible to pass properties as generics and that kind of thing.
EDIT: I have this working generically for a single search term.
Where [query.Data == "Foo1"]
return dbQuery.Where(SearchMatch("Name", query.Data));
public Expression<Func<Foo, bool>> SearchMatch(string propertyName, string searchTerm)
{
var foo = Expression.Parameter(typeof(Foo), "foo");
var prop = Expression.Property(foo, propertyName);
var search = Expression.Constant(searchTerm);
var equal = Expression.Equal(prop, search);
return Expression.Lambda<Func<Foo, bool>>(equal, foo);
}
Anyone have any ideas how to make it work for an array of strings?
You need to define an interface that exposes the properties that you want to access, like so:
public interface IHaveName
{
string Name { get; }
}
Then, on your classes, you would implement the interface:
public class Foo : IHaveName
If you're using the classes generated from a DBML file, these classes are marked with the partial keyword so implementing the interface is as simple as creating a new file, and inserting:
public partial class Foo : IHaveName
Since the property is already declared as public in the other .cs file generated from the .dbml file, the interface is implemented implicitly.
Finally, you would rewrite your ByName method to take a generic type parameter with a constraint that it implement your interface IHaveName:
private IQueryable<T> ByName<T>(IQueryable<T> dbQuery, Query query)
where T : IHaveName
{
// Everything else is the same.
For your other properties (and methods which use them), you could aggregate them together into one interface, or separate them out, depending on your needs.
Based on your edit, if you want to create an expression dynamically, you don't have to give up compile-time safety:
public Expression<Func<Foo, bool>> SearchMatch(
Expression<Func<Foo, string>> property, string searchTerm)
{
var foo = Expression.Parameter(typeof(Foo), "foo");
// Get the property info from the property expression.
var prop = Expression.Property(foo,
(property.Body as MemberExpression).Member as PropertyInfo);
var search = Expression.Constant(searchTerm);
var equal = Expression.Equal(prop, search);
return Expression.Lambda<Func<Foo, bool>>(equal, foo);
}
Which you then call like so:
var expression = SearchMatch(f => f.Name, "searchTerm");
This ensures that the properties that you are passing to SearchMatch actually exist on Foo. Note if you wanted to make this generic for other scalar property types, you would do the following:
public Expression<Func<Foo, bool>> SearchMatch<T>(
Expression<Func<Foo, T>> property, T searchTerm)
{
var foo = Expression.Parameter(typeof(Foo), "foo");
// Get the property info from the property expression.
var prop = Expression.Property(foo,
(property.Body as MemberExpression).Member as PropertyInfo);
var search = Expression.Constant(searchTerm);
var equal = Expression.Equal(prop, search);
return Expression.Lambda<Func<Foo, bool>>(equal, foo);
}
If I understood what you are trying to achieve reflection might help you. At least if you play nice. Here's a simplified but working example
internal class Program
{
private class Data
{
public string Name { get; set; }
public string Address { get; set; }
public override string ToString()
{
return String.Format("My name is {0} and I'm living at {1}", Name, Address);
}
}
static Expression<Func<Data,bool>> BuildExpression(PropertyInfo prop, IQueryable<string> restrict)
{
return (data) => !restrict.Any(elem => elem == prop.GetValue(data, null));
}
static IQueryable<Data> FilterData(IQueryable<Data> input, Expression<Func<Data, bool>> filter)
{
return input.Where(filter);
}
public static void Main (string[] args)
{
List<Data> list = new List<Data>()
{
new Data {Name = "John", Address = "1st Street"},
new Data {Name = "Mary",Address = "2nd Street"},
new Data {Name = "Carl", Address = "3rd Street"}
};
var filterByNameExpression = BuildExpression(typeof (Data).GetProperty("Name"),
(new List<string> {"John", "Carl"}).AsQueryable());
var filterByAddressExpression = BuildExpression(typeof(Data).GetProperty("Address"),
(new List<string> { "2nd Street"}).AsQueryable());
IQueryable<Data> filetedByName = FilterData(list.AsQueryable(), filterByNameExpression);
IQueryable<Data> filetedByAddress = FilterData(list.AsQueryable(), filterByAddressExpression);
Console.WriteLine("Filtered by name");
foreach (var d in filetedByName)
{
Console.WriteLine(d);
}
Console.WriteLine("Filtered by address");
foreach (var d in filetedByAddress)
{
Console.WriteLine(d);
}
Console.ReadLine();
}
Hovewer, I'\m almost sure it won't work with LINQ-to-SQL. One way to workaround it is to materialize the IQueryable before passing it to such filtering methods (e.g. by calling ToList on them).
My first (and really horrible post) is below.
I try to do a complete example what I want to get. I hope this will be left explained a bit better.
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Boy> boys = new List<Boy>();
boys.Add(new Boy("Jhon", 7));
boys.Add(new Boy("Oscar", 6));
boys.Add(new Boy("Oscar", 7));
boys.Add(new Boy("Peter", 5));
ClassRoom myClass = new ClassRoom(boys);
Console.WriteLine(myClass.ByName("Oscar").Count); // Prints 2
Console.WriteLine(myClass.ByYearsOld(7).Count); // Prints 2
// This has errors...................
// But this is as I would like to call my BySomeConditions method....
Console.WriteLine( // It should print 1
myClass.BySomeConditions([myClass.ByName("Oscar"),
myClass.ByYearsOld(7)]
)
);
Console.ReadKey();
}
class ClassRoom
{
private List<Boy> students;
public ClassRoom(List<Boy> students)
{
this.students = students;
}
public List<Boy> ByName(string name)
{
return students.FindAll(x => x.Name == name);
}
public List<Boy> ByYearsOld(int yearsOld)
{
return students.FindAll(x => x.YearsOld == yearsOld);
}
// This has ERRORS.......................
public List<Boy> BySomeConditions(params Func<X, List<Boy>>[] conditions)
{
IEnumerable<Boy> result = students;
foreach (var condition in conditions) {
// I want it ONLY be called with existent functions (ByName and/or ByYearsOld)
result = result.Intersect(condition(this));
}
}
}
class Boy
{
public string Name { get; set; }
public int YearsOld { get; set; }
public Boy(string name, int yearsOld)
{
Name = name;
YearsOld = yearsOld;
}
}
}
}
============== first post =====================
Hello,
I have a class with methods:
public class X
{
private readonly List<string> myList;
public X(List<string> paramList) // string is really an object
{
myList = paramList;
}
// Now I want this...
public List<string> CheckConditions(params Func<T, List<string>>[] conditions)
{
var result = myList;
foreach (Func<T, List<string>> condition in conditions)
{
result = result.Intersect(condition(T));
}
}
public List<string> check1(string S)
{
return myList.FindAll(x => x.FieldS == S);
}
public List<string> check1(int I)
{
return myList.FindAll(x => x.FieldI == I);
}
}
Sorry if there is some error, I have written from scrach to avoid complex real case.
What I want is call my methods like this:
X.check1("Jhon");
or
X.check2(12);
or (this is the goal of my question):
X.CheckConditions(X.check1("Jhon"), X.chek2(12));
Thanks and sorry by my poor example...
It is unclear where your T comes from.
Does this meet your requirements?
public class X<T>
{
private List<T> myList;
public List<T> CheckConditions(params Func<T, bool>[] conditions)
{
IEnumerable<T> query = myList;
foreach (Func<T, bool> condition in conditions)
{
query = query.Where(condition);
}
return query.ToList();
}
}
Then later:
List<T> result = X.CheckConditions(
z => z.FieldS == "Jhon",
z => z.FieldI == 12
);
You need to change the method signature of CheckConditions, it's accepting a variable number of List<string>, not functions.
public List<string> CheckConditions(params List<string>[] lists)
The return type of check1 is List<string>, so that needs to be the type of the parameter that CheckConditions accepts.
There's no reason to make it generic, you know that you want to operate on the current instance of X (so pass in this, instead of the T type parameter). You need to cleanup a few things to to get it to compile (return result and make the type of result and the Intersect call compatible). You can define it like this:
public List<string> CheckConditions(params Func<X, List<string>>[] conditions)
{
IEnumerable<string> result = myList;
foreach (var condition in conditions)
{
result = result.Intersect(condition(this));
}
return result.ToList();
}
Ant then call it like this:
xInstance.CheckConditions(x => x.check1("JHon"), x => x.check1(12));
All that said, I'm not sure why you wouldn't just pass around the results of these functions, instead of passing the actual functions around:
public List<string> CheckConditions(params List<string>[] conditions)
{
IEnumerable<string> result = myList;
foreach (var condition in conditions)
{
result = result.Intersect(condition);
}
return result.ToList();
}
Then call it as in your example, rather than passing in lambda expressions.
you could rewrite you function to look like this:
// Now I want this...
public List<string> CheckConditions(params Func<T, List<string>>[] conditions)
{
var result = myList;
foreach (Func<T, List<string>> condition in conditions)
{
result = result.Intersect(condition(T));
}
}
your call would then be X.CheckConditions(()=>X.check1("Jhon"), ()=>X.chek2(12));
and you need to provide an instance for x (since the methods are instance methods and not static methods)
In your example you pass T as an argument to the functor but T is a type argument som it can't be passed as an argument to the method. Did you mean to pass a value?
This begs for a clarification of why you would want to do this. Maybe if you provided details on what you are trying to accomplish (as opposed to how) then you could get a better solution to your problem.
What you pass to your
X.CheckConditions
is not a reference to the functions, but the returned value of their invocation.
Now, if you pass function reference - it does not come with parameters, unless you construct and pass a data-structure that will contain the function reference and the arguments it should work on.
In this case - generics is not the solution. You should consider another pattern to follow, like command pattern or strategy pattern, where you pass to your CheckConstruction instances of checker-objects, each is instantiated with the parameters it should work on, and either implements or is provided by the validation function.
I am creating a Distinct extension method where I can pass in the criteria like the following.
persons.Distinct(p => p.Name);
I got the code from the web but I am having a hard time understanding the purpose of Func<T, TResult>. Also, when I say p => p.Name am I sending the String Name or am I sending the complete Person object? Here is the new Distinct method:
public static class ExtensionMethods
{
public static IEnumerable<T> Distinct<T>(
this IEnumerable<T> list, Func<T,object> checker)
{
return list.Distinct(new GenericComparer<T>(checker));
}
}
public class GenericComparer<T> : IEqualityComparer<T>
{
private Func<T, object> _checker;
public GenericComparer(Func<T,object> checker)
{
_checker = checker;
}
public bool Equals(T x, T y)
{
return _checker(x).Equals(_checker(y));
}
public int GetHashCode(T obj)
{
return _checker(obj).GetHashCode();
}
}
And here is the usage:
static void Main(string[] args)
{
var persons = new List<Person>()
{
new Person() { Id = 1, Name = "Mary"},
new Person() {Id = 2, Name="John"},
new Person() { Id = 3, Name = "Mary"}
};
var uniquePersons = persons.Distinct(p => p.Name);
foreach(var person in uniquePersons)
{
Console.WriteLine(person.Name);
}
}
When you do this:
persons.Distinct(p => p.Name);
You're basically creating a function on the fly (using lambda expressions), that looks like this:
string theFunction(Person p)
{
return p.Name;
}
This is a function that fits the signature of a Func<Person,String> delegate. The Distinct method can take a delegate (basically a function pointer) which it uses to determine whether or not an element is distinct - in your case, only unique strings (returned by the function above) will be considered "distinct" elements. This delegate is run on each element of your "persons" enumerable, and the results of those functions are used. It then creates a sequence (IEnumerable<Person>) from those elements.
Func<T, TResult>
defines a function that accepts one parameter (of type T) and returns an object (of type TResult).
In your case, if you want a function that takes a Person object and returns a string...you'd want
Func<Person, string>
which is the equivalent of:
string Function(Person p)
{
return p.Name;
}
You are getting back the distinct People, under the assumption that two People are the same if they have the same name
If you want a distinct set of names, you can use this:
IEnumerable<String> names = persons.Select(p => p.Name).Distinct();