Show Properties of a Navigation Property in DataGridView (Second Level Properties)
I'm trying to display several properties from a related entity on a DataGridView
in a winforms app. It seems pretty ordinary to me but I'm having trouble finding examples. It's an order entry operation. OrderSheet data, the ID and the pickup date for the order, then the line items (OrderSheetItems in the model below) in the grid. The order lineitems have a navigation property, Product, based on the ProductId. I can use a DataGridViewComboBoxColumn with ProductId as ValueMember and another field as DisplayMember. But I want to include more data in other columns, size, color, material, etc.
Here's the code for loading the data
try
{
_context.OrderSheets.Include(o => o.OrderSheetItems.Select(i => i.Product)).Load();
orderSheetBindingSource.DataSource = _context.OrderSheets.Local.ToBindingList();
}
catch (Exception ex)...
The ProductId is in a separate column just for experimenting, that will be the combobox later. So is there a way to bind the other columns to the data in Product navigation property of the OrderSheetItem or do I have to handle CellValueChanged on the product id to physically set the data in the other columns? If there's a way to bind the columns then would that be via code in OnLoad or somewhere in the grid view columns designer?
TIA, Mike
Solution 1:
You can use either of these options:
- Use
DataGridViewComboBoxColumn
- Add corresponding properties to child entity partial class
- Shape the query to include properties of navigation property using
Linq
- Use
CellFormatting
event to get value for sub property bounded columns - Show string representation of object by overriding
ToString()
- Use a custom
TypeDescriptor
to enable data binding to sub properties.
Option 1 - Use DataGridViewComboBoxColumn
Usage: This approach would be useful specially in cases which you want to keep the control editable.
In this approach you can use DataGridViewComboBoxColumn
to show any field of navigationn property. To show multiple field sub properties of navigation property in grid, use multiple DataGridViewComboBoxColumn
bound to same navigation property with different DisplayMember
In this approach, additional to your ProductId
column, add more DataGridViewComboBoxColumn
to the grid and then perform these settings for all additional combo columns:
- Set
DataPropertyName
of them toProductId
- Set the
DataSource
property of them, to exactly the same data source you used for mainProductId
column, for exampleproductBindingSource
- Set
ValueMember
of them to the same value member you set for product id column, it's the key column of your product table.(ProductId
) - Set
DisplayMember
for each of them to a column that you want to show, for example, set one of them to Name. one to Price, one to Size, ... . This way you can show related entity fields. - Set
ReadOnly
property of them totrue
. It makes the cell read only. - If you want to make columns readonly Set
DisplayStyle
property of them toNothing
. It removes dropdown style.
If you want to keep ProductId
editable, keep the DisplayStyle
of it to DropDownButton
. This way when you change the value of ProductId
column using combobox, when you leave the row and moved to next row, you will see other cells of row, shows other properties of the selected product. Also since the other combobox columns are read only and have no combobox style, the user can not change the value of them and they act only like a read only text box column that show other properties from related entity.
Option 2 - Add corresponding properties to child entity partial class
Usage: This approach would be useful when you don't need to edit values.
In this approach, You can define properties in child entity partial class return value of corresponding property of parent entity. For example for product name, define this property in order item partial class:
public string ProductName
{
get
{
if (this.Product != null)
return this.Product.Name;
else
return string.Empty;
}
}
Then you can simply include products when selecting order items and bind the grid column to corresponding properties of order item.
Option 3 - Shape the query to include properties of navigation property
Usage: This approach would be useful when you don't need to edit values.
You can shape the query to include properties of navigation property. You can use an anonymous object or a View Mode simply, for example:
var list = db.OrderDetails.Include("Products").Where(x=>x.OrderId==1)
.Select(x=> new OrderDetailVM() {
Id = x.Id,
ProductId = x.ProductId,
ProductName = x.Product.Name,
Price = x.Product.Price
}).ToList();
Option 4 - Use CellFormatting event to get value for sub property bounded columns
Usage: This approach would be useful when you don't need to edit values.
In this approach you can use CellFormatting
event of DataGridView
. You can simply set e.Value
based on column index. For example:
void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
//I Suppose you want to show product name in column at index 3
if(e.RowIndex>=0 && e.ColumnIndex==3)
{
var orderLineItem= (OrderLineItem)(this.dataGridView1.Rows[e.RowIndex]
.DataBoundItem);
if (order!= null && orderLineItem.Product != null)
e.Value = orderLineItem.Product.Name);
}
}
You can use different criteria to handle different columns and show different sub properties.
Also you can make it more dynamic and reusable using reflection. You can extract the value of sub property of navigation property using reflection. To do so you should create column and set DataPropertyName
to sub properties like Product.Name
then in CellFormatting
event, using reflection, get the value for column. Here is a good article by Antonio Bello about this approach:
- DataGridView: How to Bind Nested Objects
Option 5 - Show string representation of object by overriding ToString()
Usage: This approach would be useful when you don't need to edit values.
If you want to show only a single column of navigation property, you can simply override ToString()
method of navigation property class and return suitable value. This way, when showing a property of that type in grid, you will see a friendly text. For example in partial class of Product
, you can write:
public override string ToString()
{
return this.Name;
}
Option 6 - Use a custom TypeDescriptor to enable data binding to sub properties
Usage: This approach would be useful when you don't need to edit values.
In this approach you can create a custom TypeDescriptor that enables you to perform data binding to second-level properties. Here is a good article by Linda Liu about this approach:
- How to bind a DataGridView column to a second-level property of a data source