Thanks to those who answered my last question I got the code below to work which allows the developer to send multiple where clauses to a method which includes each of them in a LINQ statement. However, how can I get the inclusion of the where clauses to be dynamic? Instead of this:
return customers
.Where(whereClauses[0])
.Where(whereClauses[1])
.ToList();
something like this (pseudo-code):
List<Customer> customers = new List<Customer>();
foreach (var whereClause in whereClauses)
{
customers
.Where(whereClause...???)
.ToList();
}
return customers;
Here is the code that works:
using System;
using System.Collections.Generic;
using System.Linq;
namespace TestDynamicLinq2343
{
public class Program
{
static void Main(string[] args)
{
List<Customer> customers = Customer.GetCustomers();
List<Func<Customer, bool>> whereClauses = new List<Func<Customer, bool>>();
whereClauses.Add(c => c.LastName.ToUpper().Contains("A"));
whereClauses.Add(c => c.FirstName.ToUpper().Contains("J"));
foreach (var customer in Customer.GetFilteredCustomers(customers, whereClauses))
{
Console.WriteLine(customer.LastName);
}
Console.ReadLine();
}
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Street { get; set; }
public string Location { get; set; }
public string ZipCode { get; set; }
public static List<Customer> GetCustomers()
{
List<Customer> customers = new List<Customer>();
customers.Add(new Customer { FirstName = "Jim", LastName = "Jones" });
customers.Add(new Customer { FirstName = "Joe", LastName = "Adams" });
customers.Add(new Customer { FirstName = "Jake", LastName = "Johnson" });
customers.Add(new Customer { FirstName = "Angie", LastName = "Reckar" });
customers.Add(new Customer { FirstName = "Jean", LastName = "Anderson" });
return customers;
}
public static List<Customer> GetFilteredCustomers(List<Customer> customers, List<Func<Customer, bool>> whereClauses)
{
return customers
.Where(whereClauses[0])
.Where(whereClauses[1])
.ToList();
}
}
}
IEnumerable<Customer> dbCustomers = customers;
foreach (var whereClause in whereClauses)
{
dbCustomers = dbCustomers.Where(whereClause);
}
return dbCustomers.ToList();
maybe interesting extensionMethod:
public static class IEnumerableExtension
{
public static void AttachWhereClauses<T>(this IEnumerable<T> source, IEnumerable<Func<T, bool>> whereClauses)
{
foreach (var whereClause in whereClauses)
{
source = source.Where(whereClause);
}
}
}
var listedCustomers = customers.AttachWhereClauses(whereClauses).ToList();
but: not tested - i do not know for sure, if attaching the whereClauses to the same object works!
Something like this:
public IEnumerable<Customer> ApplyConditions(IEnumerable<Func<Customer, bool>> conditions)
{
IEnumerable<Customer> customers = ...;
foreach(var condition in conditions)
customers = customers.Where(condition);
return customers;
}
Related
I find it hard to clearly describe the case in a one-sentence title. Here is the example:
public class Person
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
}
public enum PersonProperties
{
FirstName = 1,
MiddleName = 2,
LastName = 3
}
I am hoping to do this:
foreach (var p in Persons) {
var nameCollection=new List<string>();
foreach (var s in (SectionsEnum[]) Enum.GetValues(typeof (SectionsEnum)))
{
nameCollection.Add(p.GetPropertyByName(s);
}
}
Now, how can we implement the GetPropertyByName() part?
You could do this directly using reflection:
public string GetPropertyByName(SectionsEnum s)
{
var property = typeof(Person).GetProperty(s.ToString());
return (string)property.GetValue(this);
}
Or maybe with a switch.
public string GetPropertyByName(SectionsEnum s)
{
switch (s)
{
case SectionsEnum.FirstName:
return this.FirstName;
case SectionsEnum.MiddleName:
return this.MiddleName;
case SectionsEnum.LastName:
return this.LastName;
default:
throw new Exception();
}
}
But I'd ask if you wouldn't be better served by a wholly different approach, e.g. a list:
public IList<string> NameProperties
{
get
{
return new[] { FirstName, MiddleName, LastName };
}
}
Or instead of having SectionsEnum, use Funcs:
//was
SectionsEnum s = SectionsEnum.FirstName;
//instead
Func<Person, string> nameFunc = p => p.FirstName;
string name = nameFunc(myPerson);
this should be a good starting point for you
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Person p = new Person() { FirstName ="a", MiddleName = "b", LastName = "c" };
List<string> result = new List<string>();
string[] enums = Enum.GetNames(typeof(PersonProperties));
foreach(string e in enums)
{
result.Add(p.GetType().GetProperty(e).GetValue(p, null).ToString());
}
int i = 0;
foreach (string e in enums)
{
Console.WriteLine(string.Format("{0} : {1}", e, result[i++]));
}
Console.ReadKey(false);
}
}
public class Person
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
}
public enum PersonProperties
{
FirstName = 1,
MiddleName = 2,
LastName = 3
}
}
It's not impossible that my question has already been answered here, but unfortunately when it comes to LINQ I can't always find the right search terms.
Given the underlying source code how do you think I can reuse Selectors.CustomerDTO selector within Selectors.CustomerExtendedDTO?
It should be possible since the input database entity is the same.
class Program
{
static void Main(string[] args)
{
using (NorthwindEntities ctx = new NorthwindEntities())
{
foreach (var item in ctx.Customers.Where(c => c.CompanyName.StartsWith("A")).Select(Selectors.CustomerDTO))
{
Console.WriteLine("CompanyName: {0}", item.CompanyName);
Console.WriteLine();
}
foreach (var item in ctx.Customers.Where(c => c.CompanyName.StartsWith("A")).Select(Selectors.CustomerExtendedDTO))
{
Console.WriteLine("CompanyName: {0}", item.CompanyName);
Console.WriteLine("ContactName: {0}", item.ContactName);
Console.WriteLine();
}
Console.ReadLine();
}
}
}
class CustomerDTO
{
public string CustomerID { get; set; }
public string CompanyName { get; set; }
}
class CustomerExtendedDTO : CustomerDTO
{
public string ContactName { get; set; }
public string Address { get; set; }
}
static class Selectors
{
internal static Expression<Func<Customers, CustomerDTO>> CustomerDTO
{
get
{
return c => new CustomerDTO
{
CustomerID = c.CustomerID,
CompanyName = c.CompanyName
};
}
}
internal static Expression<Func<Customers, CustomerExtendedDTO>> CustomerExtendedDTO
{
get
{
return c => new CustomerExtendedDTO
{
CustomerID = c.CustomerID,
CompanyName = c.CompanyName,
Address = c.Address,
ContactName = c.ContactName
};
}
}
}
Such thing would be very useful when the inherited projection entity does not only contain two properties but several dozens.
Thanks for any ideas.
I'm looking for a LINQ delete method for this:
<ArrayOfGroup>
<Group>
<GroupName>c</GroupName>
<StudentList>
<Student>
<TimeAdded>0001-01-01T00:00:00</TimeAdded> // trying to delete from here
<StudentID>1</StudentID>
<FirstName>a</FirstName>
<LastName>b</LastName> // to here
<StudentGroup/>
</Student>
</StudentList>
</Group>
</ArrayOfGroup>
For some reason when I delete it this way:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class Service : IService
{
List<Student> students = new List<Student>();
List<Group> Groups = new List<Group>();
public void removeStudent(string studentID)
{
students.Remove(students.Find(f => f.StudentID.Equals(studentID)));
//var result = students.Where(n => String.Equals(n.StudentID, studentID)).FirstOrDefault();
//if (result == null)
//{
// result.StudentGroup.Remove(StudentList.Student.Find(f => f.StudentID.Equals(studentID)));
//}
// students.RemoveAll(f => f.StudentID.Equals(studentID)); also tryed this
}
It doesn't remove it from the Student List/ student area of my ArrayOfGroup, so I am fed up trying to solve why it isn't deleting and just trying to find a new way to delete it along with the original records. I feel like a criminal for doing this but if anyone can help would be much appreciated.
My Data Contracts looks like this:
[DataContract(Name="Student")]
public class Student
{
public Student()
{
StudentGroup = new List<Group>();
}
[DataMember(Name = "StudentID")]
public string StudentID { get; set; }
[DataMember(Name = "FirstName")]
public string FirstName { get; set; }
[DataMember(Name = "LastName")]
public string LastName { get; set; }
[DataMember(Name = "TimeAdded")]
public DateTime TimeAdded;
public string TimeAddedString
{
get
{
return this.TimeAdded.ToString("dd/MM/yyyy hh:mm:ss");
}
}
public List<Group> StudentGroup { get; set; }
}
[DataContract(Name = "Group")]
public class Group
{
public Group()
{
StudentList = new List<Student>();
}
[DataMember(Name = "GroupName")]
public string GroupName { get; set; }
public List<Student> StudentList { get; set; }
}
This is how a student is added to a group if it helps?
public void AddStudentToGroup(string group, string studentID, string firstName, string lastName)
{
var result = Groups.Where(n => String.Equals(n.GroupName, group)).FirstOrDefault();
var result1 = students.Where(n => String.Equals(n.StudentID, studentID)).FirstOrDefault();
if (result != null)
{
result.StudentList.Add(new Student() { StudentID = studentID, FirstName = firstName, LastName = lastName });
}
if (result1 != null)
{
result1.StudentGroup.Add(new Group() { GroupName = group });
}
}
You can use the Remove method on the List<T> object which takes a predicate like so:
students.RemoveAll(s => s.StudentId == 5);
Looking for an example where I can filter my collection based on some filtering criteria.
I have been looking for some example where given a list /array i can filter a collection.
In the example below in my find method I am trying to filter based on 2 values ,looking for something like an "IN" function any suggestions?
class Program
{
static void Main()
{
//Print all customres that belong to below deparments and match on surname
var criteria=new Criteria
{
Departments = new List<string> {"BusinessAnalyst", "Account"},
Surname = "Bloggs"
};
List<Customer> customers = Repository.Find(criteria);
customers.ForEach(x => Console.WriteLine(string.Format("Surname: {0} Department :{1}", x.Surname,x.Department)));
Console.Read();
}
}
public class Repository
{
public static List<Customer>GetCustomers()
{
return new List<Customer>
{
new Customer { Name = "Jon",Surname="Smith",Department = DepartmentType.Managers},
new Customer{Name = "Bill",Surname = "Gates",Department = DepartmentType.Managers},
new Customer { Name = "Mary",Surname = "Bug",Department = DepartmentType.Developers},
new Customer { Name = "Mark",Surname="Boo",Department = DepartmentType.Account},
new Customer{Name = "Ron",Surname = "Scott",Department = DepartmentType.Managers},
new Customer { Name = "Jonny",Surname = "Dip",Department = DepartmentType.Developers},
new Customer { Name = "Mary",Surname = "Bloggs",Department = DepartmentType.BusinessAnalyst},
new Customer { Name = "Mary",Surname = "Bug",Department = DepartmentType.Account},
new Customer { Name = "Jonny",Surname = "Dip",Department = DepartmentType.Account},
new Customer { Name = "Mary",Surname = "Bloggs",Department = DepartmentType.Managers}
};
}
public static List<Customer> Find(Criteria criteria)
{
List<Customer>customers=Repository.GetCustomers();
//Filter on departments
//ERROR HERE AS I cannot do this "IN" would be fantastic.
customers = customers.Contains(criteria.Departments);
//now filter on name
customers = customers.Where(x => x.Surname == criteria.Surname).ToList();
return customers;
}
}
public enum DepartmentType
{
Account,
Managers,
Developers,
BusinessAnalyst
}
public class Customer
{
public string Name { get; set; }
public string Surname { get; set; }
public DepartmentType Department { get; set; }
}
public class Criteria
{
public Criteria()
{
Departments=new List<string>();
}
public string Name { get; set; }
public string Surname { get; set; }
public List<string> Departments { get; set; }
}
public static List<Customer> Find(Criteria criteria)
{
List<Customer> customers = Repository.GetCustomers();
var customers2 = customers.Where(x => criteria.Departments.Contains(x.Department.ToString()));
var customers3 = customers2.Where(x => x.Surname == criteria.Surname);
return customers3.ToList();
}
But considering you use an enum for the Department (DepartmentType), shouldn't your Criteria class use the same instead of a string?
If you define the criteria.Departments as List<DepartmentType> then you can write
public static List<Customer> Find(Criteria criteria)
{
List<Customer> customers = Repository.GetCustomers();
var customers2 = customers.Where(x => criteria.Departments.Contains(x.Department));
var customers3 = customers2.Where(x => x.Surname == criteria.Surname);
return customers3.ToList();
}
Contains returns a bool defining whether a specified object is contained in a collection. Based on your example, you will need to use Where to filter the customers, then use Contains on the departments:
customers = customers.Where(c => criteria.Departments.Contains(c.Department));
i think you want something like this..
customers = customers.Where(c => criteria.Departments.Contains(c.Department));
You want
Customers.Where(c => criteria.Departments.Contains(c.Department.ToString()))
Not sure if this is what you're looking for but the following:
List<Customer> FilteredCustomers = (from c in customers where Criteria.Departments.Contains(c.deparment) && c.surname == Criteria.Surname select c).ToList();
Would equate to something like this in SQL:
SELECT *
FROM Customers
WHERE Department IN (
List of departments
)
AND Surname = surname
I haven't tested this but I think it should work and bring back what you want.
In the following example, GetFilteredCustomers() works fine so I can send various letters which I want customers to have in their last name.
But how can I build GetFilteredCustomersDynamic() which will enable me to send a full where clause that can be dynamically included in the LINQ statement?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestDynamicLinq2343
{
public class Program
{
static void Main(string[] args)
{
List<Customer> customers = Customer.GetCustomers();
//semi-dynamic
foreach (var customer in Customer.GetFilteredCustomers(customers, "o"))
{
Console.WriteLine(customer.LastName);
}
//fully-dyanmic (can send where clauses)
foreach (var customer in Customer.GetFilteredCustomersDynamic(customers, c => c.FirstName.Contains("a")))
{
Console.WriteLine(customer.LastName);
}
Console.ReadLine();
}
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Street { get; set; }
public string Location { get; set; }
public string ZipCode { get; set; }
public static List<Customer> GetCustomers()
{
List<Customer> customers = new List<Customer>();
customers.Add(new Customer { FirstName = "Jim", LastName = "Jones" });
customers.Add(new Customer { FirstName = "Joe", LastName = "Adams" });
customers.Add(new Customer { FirstName = "Jake", LastName = "Johnson" });
return customers;
}
public static List<Customer> GetFilteredCustomers(List<Customer> customers, string letter)
{
return (from c in customers
where c.LastName.Contains(letter)
select c).ToList();
}
public static List<Customer> GetFilteredCustomersDynamic(List<Customer> customers, Func<..., bool> whereClause)
{
return (from c in customers
where ...whereClause...
select c).ToList();
}
}
}
Answer:
thanks elder_george and arjuns, I got this example to work like this (albeit without the Expression<> ):
using System;
using System.Collections.Generic;
using System.Linq;
namespace TestDynamicLinq2343
{
public class Program
{
static void Main(string[] args)
{
List<Customer> customers = Customer.GetCustomers();
Func<Customer, bool> whereClause = c => c.LastName.Contains("a") && c.FirstName.Contains("J");
foreach (var customer in Customer.GetFilteredCustomers(customers, whereClause))
{
Console.WriteLine(customer.LastName);
}
Console.ReadLine();
}
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Street { get; set; }
public string Location { get; set; }
public string ZipCode { get; set; }
public static List<Customer> GetCustomers()
{
List<Customer> customers = new List<Customer>();
customers.Add(new Customer { FirstName = "Jim", LastName = "Jones" });
customers.Add(new Customer { FirstName = "Joe", LastName = "Adams" });
customers.Add(new Customer { FirstName = "Jake", LastName = "Johnson" });
customers.Add(new Customer { FirstName = "Angie", LastName = "Reckar" });
customers.Add(new Customer { FirstName = "Jean-Luc", LastName = "Beaudoir" });
return customers;
}
public static List<Customer> GetFilteredCustomers(List<Customer> customers, Func<Customer, bool> whereClause)
{
return customers
.Where(whereClause).ToList();
}
}
}
You'd need to represent filter as Expression<Func<Customer, bool>>, not as Func<Customer, bool>. This way you can use Queryable.Where method to add this filter to LINQ expression tree.
EDIT: I was wrong, as this code uses LINQ to objects, where delegates are proper filter criteria. My bad.
E.g. (corrected to using normal delegates):
public static List<Customer> GetFilteredCustomersDynamic(List<Customer> customers, Func<Customer, bool> whereClause)
{
return customers
.Where(whereClause).ToList();
}
public static List<Customer> GetFilteredCustomers(List<Customer> customers, string letter)
{
return GetFilteredCustomersDynamic(c => c.LastName.Contains(letter));
}
Try this code,
public static List<Customer> GetFilteredCustomersDynamic(List<Customer> customers, Expression<Func<Customer, bool>> whereClause)
{
return customers.Where(whereClause.Compile()).ToList();
}
#elder_george,
there is a typo, expression should be compiled to get it's delegate otherwise this can no compile.
return customers
.Where(whereClause).ToList();
should be read as
return customers
.Where(whereClause.Compile()).ToList();