问题
I am currently unsure on the best way of going about getting a filter to work right on a datagridview that has its datasource set to a list of objects.
So given an object of:
public class DepositAccountBill
{
#region Properties
public int AccountBillID { get; set; }
public int AccountID { get; set; }
public string AccountNumber { get; set; }
public string ControlNumber { get; set; }
public DateTime BillDate { get; set; }
public decimal DepositAmount { get; set; }
}
I have a datagridview table that looks roughly like this:
Account Number | Control Number | Bill Date | Deposit Amount
==================================================================
123456 | AJA1234367 | 5/21/2018 | 12.99
123456 | PSA1234367 | 5/21/2018 | 5.77
567332 | HBA1234367 | 5/21/2018 | 1255.99
769843 | AJA1234367 | 5/21/2018 | 12.99
So when clicking on a cell. Lets say the first cell on the first column. If I right click a cell and select an option in a context Menu that says filter I need to display the datagridview table with only rows that have the same Account number. In this case, it would be rows 1 and 2. In order for me to do this, I have to access the object that the datagridview table is populated with DepositAccountBill. So what I need to do is look and at the column I'm looking at has the selected cell's value.
So in my method I have tried thus far with no results:
var collection = (List<DepositAccountBill>)dataGridView1.DataSource;
var filterList = collection.Where ( q => (collection.Select(r => GetPropValue(r, dataGridView1.Columns[clickedCell.ColumnIndex].DataPropertyName))) == (clickedCell.Value);
dataGridView1.DataSource = filterList.ToList();
public object GetPropValue(object obj, string propName)
{
return obj.GetType().GetProperty(propName).GetValue(obj, null);
}
I don't know if SELECT is the right LINQ method to use here or if this is even possible. I want to use WHERE because it only grabs the list of objects that match the condition. Something like this:
var filterList = collection.Where(r => r.AccountNumber == clickedCell.Value);
Only problem is the r.AccountNumber
is dependant on the data property of the selected column. The program does not know what the data property is based on a click event on the selected cell. This is why I think reflection might be necessary.
回答1:
I suppose you know how to extract the property name using DataPropertyName
property of the column and also how to get the value from Value
property of the cell.
In the answer I'll focus on how to perform dynamic filtering a List<T>
having Property Name and the Value, like when you use .Where(x=>x.PropertyName == Value)
.
To do so, you have a couple of options which you can choose, including:
- Dynamic Linq Library
- Creating
Expression
dynamically at run-time - Using a
Dictionary
containing criteria that you need for each property - Simply using if/else
I'll share more details about above solutions and you can choose either of them.
Note: If the answer is too long, probably the easiest and most obvious answer would be 4th option.
Option 1 - Dynamic Linq Library
As an option you can add System.Linq.Dynamic
reference to your project, then after using System.Linq.Dynamic
namespace, you will be able to create dynamic queries on IEnumerable<T>
or IQueryable<T>
by passing criteria as string
, for example:
var list = db.Products.ToList();
var result = list.Where("Name = @0", "product1").ToList();
This way you can simply use the column data property name and column value in the query dynamically. To find more about dynamic linq, take a look at following resources:
NuGet package for
System.Linq.Dynamic
. You can install the package simply using this command in package manager console:Install-Package System.Linq.Dynamic
GitHub repository for
System.Linq.Dynamic
Scott Guthrie's blog post about Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library)
Option 2 - Create Lambda Expression at run-time
As another option you can create lambda expression at run-time and use it with Where
method. For example, you can create the following method:
//Creates x=>x.Something == value
public Expression<Func<T, bool>> EqualCriteria<T>(string propertyName, object value)
{
var property = typeof(T).GetProperty(propertyName);
var x = Expression.Parameter(typeof(T), "x");
var propertyExpression = Expression.Property(x, property.Name);
var valueExpression = Expression.Convert(Expression.Constant(value),
property.PropertyType);
var criteria = Expression.Equal(propertyExpression, valueExpression);
var lambda = Expression.Lambda<Func<T, bool>>(criteria, x);
return lambda;
}
And then use it this way:
var list = db.Products.ToList();
var result = list.Where(EqualCriteria<Product>("Name", "product1").Compile()).ToList();
As you can see, also in this solution, you can use column data property name and value dynamically.
Option 3 - Using predefined criteria in a dictionary
As another option you can create a dictionary containing the criteria which you need, then use them dynamically. For example let's say you've created the following dictionary:
var criterias = new Dictionary<string, Func<Product, object, bool>>() {
{ "Id" , (p,v)=>p.Id.Equals(v) },
{ "Name" , (p,v)=>p.Name.Equals(v) }
};
Then based on the column data property name and cell value, you can simply use the following statement to filter the list:
var list = db.Products.ToList();
var result = list.Where(x => criterias["Name"](x, "product1")).ToList();
Option 4 - Using if/else
As another option you can use if/else conditions simply. For example let's say you've the value in an object variable and property name in a string variable, then you can write:
var list = db.Products.ToList();
var result = list.ToList();
if (propertyName == "Id")
{
result = result.Where(x=>x.Id == (int)value).ToList();
}
else if (propertyName == "Name")
{
result = result.Where(x=>x.Name == (string)value).ToList();
}
来源:https://stackoverflow.com/questions/50473727/filtering-list-of-objects-in-datagridview-based-on-cell-value