Show Properties of a Navigation Property in DataGridView (Second Level Properties)
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
How to bind a Navigation Property (second level properties) in DataGridView using BindingSource?
Options to show Second Level Properties in DataGridView
To show a sub property of your navigation property you can use either of these options:
Use a
DataGridViewComboBox
column and bind it toCompanyId
and set it'sDataSource
to list of companies, andDisplayMember
property toName
property of company andValueMember
toId
property of company.Override
ToString()
method ofCompany
class and returnName
of company. Then showCompany
navigation property in grid.Create a
CompanyName
property for yourEstimate
which returns itsCompany.Name
value and showCompanyName
in grid.Using
CellFormatting
event ofDataGridView
and sete.Value
to desired value (company name) you want to display in cell.Shape your Estimates list using a
Linq
query or use aViewModel
and pass the result to data grid view.Create a
TypeDescriptor
for yourEstimate
type to resolve second level properties.
.
To show a property of company instead of company id, you can use aDataGridViewComboBoxColumn
.
Using ComboBox Column
Since you requested for a mechanism which uses designer without writing code I describe this option more. Here is settings you should perform:
EstimatesBindingSource
should bind to a list ofEstimates
- The
DataGridView
should bind toEstimatesBindingSource
CompanyBindingSource
is only used as data source of the combo box column and should be filled using a list ofCompanies
- To show
CompanyName
inEstimates
list, it's enough to use aDataGridViewComboBoxColumn
and set it'sDataSource
to list of companies and set theDisplayMember
toCompanyName
and it's value member toId
. And bind it toCompanyId
field ofEstimate
.
Also if your requirement is to don't show it as ComboBox
, simply set DisplayStyle
property of DataGridViewComboBoxColumn
to Nothing
. It removes dropdown style.
You also may find this post helpful:
- Show Properties of a Navigation Property in DataGridView (Second Level Properties)
How to bind Navigation Property (second level model's two properties) in DataGridView's single column using BindingSource?
Option 1 - CellFormatting
As an option you can use CellFormatting
event of DataGridView
and show desired value:
void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
//I Suppose you want to show full name in column at index 3
if(e.RowIndex>=0 && e.ColumnIndex==3)
{
var company = (Company)(this.dataGridView1.Rows[e.RowIndex].DataBoundItem);
if (company != null && company.Partner != null)
e.Value = string.Format("{0} {1}", company.Partner.FirstName,
company.Partner.LastName);
}
}
Option 2 - ToString()
As another option you can override ToString()
method of Partner
class and show the Partner
in a column:
public override string ToString()
{
return string.Format("{0} {1}", this.FirstName, this.LastName);
}
Using ComboBox to show second-level properties in DataGridView
To support the post Show Properties of a Navigation Property in DataGridView (Second Level Properties) I've already shared a few example in the same post or this one which allows showing second level properties and allow editing them.
Here I will share a few more examples, each example has been written as a minimal complete verifiable example, you can just copy and paste in an empty form and they will work.
These are the examples:
- Using ToString()
- Using CellFormatting
- Using ComboBox Column for Navigation Object
- Using ComboBox Column for Foreign key column
Example - Using ToString()
When: You don't want to change JobTitle
of Employee
How: By overriding ToString
method of JobTitle
class JobTitle
{
public int Id { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public JobTitle JobTitle { get; set; }
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
var jobTitles = new List<JobTitle>() {
new JobTitle {Id= 1, Name="Manager" },
new JobTitle {Id= 2, Name="Employee" },
};
var employees = new List<Employee>() {
new Employee{ Id = 1, Name ="John", JobTitle = jobTitles[0] },
new Employee{ Id = 2, Name ="Jane", JobTitle = jobTitles[1] },
new Employee{ Id = 3, Name ="Jack", JobTitle = jobTitles[1] },
};
var dg = new DataGridView();
dg.Dock = DockStyle.Fill;
dg.DataSource = employees;
this.Controls.Add(dg);
}
Example - Using CellFormatting
When: You don't want to change JobTitle
of Employee
How: By handling CellFormatting
event of DataGridView
and setting Value
of the event args to a string representation of JobTitle
class JobTitle
{
public int Id { get; set; }
public string Name { get; set; }
}
class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public JobTitle JobTitle { get; set; }
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
var jobTitles = new List<JobTitle>() {
new JobTitle {Id= 1, Name="Manager" },
new JobTitle {Id= 2, Name="Employee" },
};
var employees = new List<Employee>() {
new Employee{ Id = 1, Name ="John", JobTitle = jobTitles[0] },
new Employee{ Id = 2, Name ="Jane", JobTitle = jobTitles[1] },
new Employee{ Id = 3, Name ="Jack", JobTitle = jobTitles[1] },
};
var dg = new DataGridView();
dg.Dock = DockStyle.Fill;
dg.DataSource = employees;
dg.CellFormatting += (obj, args) =>
{
if (args.RowIndex >= 0 &&
dg.Columns[args.ColumnIndex].DataPropertyName == "JobTitle")
args.Value = ((Employee)dg.Rows[args.RowIndex].DataBoundItem).JobTitle.Name;
};
this.Controls.Add(dg);
}
Example - Using ComboBox Column for Foreign key column
When: You want to be able to change the JobTitle
of Employee
and you have the foreign key column in your model.
How: Using a DataGridViewComboBoxColumn
for that property, having a data source containing all job titles, and setting DisplayMember
and ValueMember
to proper properties.
class JobTitle
{
public int Id { get; set; }
public string Name { get; set; }
}
class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int JobTitleId { get; set; }
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
var jobTitles = new List<JobTitle>() {
new JobTitle {Id= 1, Name="Manager" },
new JobTitle {Id= 2, Name="Employee" },
};
var employees = new List<Employee>() {
new Employee{ Id = 1, Name ="John", JobTitleId = 1 },
new Employee{ Id = 2, Name ="Jane", JobTitleId = 2 },
new Employee{ Id = 2, Name ="Jack", JobTitleId = 2 },
};
var dg = new DataGridView();
dg.Dock = DockStyle.Fill;
dg.DataSource = employees;
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
DataPropertyName = "Id", HeaderText = "Id"
});
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
DataPropertyName = "Name", HeaderText = "Name"
});
dg.Columns.Add(new DataGridViewComboBoxColumn()
{
DataPropertyName = "JobTitleId",
HeaderText = "JobTitleId",
DataSource = jobTitles,
ValueMember = "Id",
DisplayMember = "Name",
});
this.Controls.Add(dg);
}
Example - Using ComboBox Column for Navigation Object
When: You want to be able to change the JobTitle
of Employee
and you don't have the foreign key column in your model, instead you want to use the navigation object in your model.
How: Using a DataGridViewComboBoxColumn
for that property, having a data source containing all job titles, without setting DisplayMember
and ValueMember
to proper properties. Then handling CellFormatting
to set the display value of the cell and handling CellParsing
to get value from ComboBox
and put into the cell.
class JobTitle
{
public int Id { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public JobTitle JobTitle { get; set; }
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
var jobTitles = new List<JobTitle>() {
new JobTitle {Id= 1, Name="Manager" },
new JobTitle {Id= 1, Name="Employee" },
};
var employees = new List<Employee>() {
new Employee{ Id = 1, Name ="John", JobTitle = jobTitles[0] },
new Employee{ Id = 2, Name ="Jane", JobTitle = jobTitles[1] },
new Employee{ Id = 2, Name ="Jack", JobTitle = jobTitles[1] },
};
var dg = new DataGridView();
dg.Dock = DockStyle.Fill;
dg.DataSource = employees;
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
DataPropertyName = "Id", HeaderText = "Id"
});
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
DataPropertyName = "Name", HeaderText = "Name"
});
dg.Columns.Add(new DataGridViewComboBoxColumn()
{
DataPropertyName = "JobTitle",
HeaderText = "JobTitle",
DataSource = jobTitles,
});
dg.CellFormatting += (obj, args) =>
{
if (args.RowIndex >= 0 &&
dg.Columns[args.ColumnIndex].DataPropertyName == "JobTitle")
{
args.Value =
((Employee)dg.Rows[args.RowIndex].DataBoundItem).JobTitle.ToString();
}
};
dg.CellParsing += (obj, args) =>
{
if (args.RowIndex >= 0 &&
dg.Columns[args.ColumnIndex].DataPropertyName == "JobTitle")
{
args.Value = ((ComboBox)dg.EditingControl).SelectedItem;
args.ParsingApplied = true;
}
};
this.Controls.Add(dg);
}
Prevent a data grid from loading Entity Framework navigation properties in C#
Loading navigation properties and showing them are two different topics.
Prevent Loading
Disable lazy loading and just include those navigation properties which you need
var db = new MyDbContext();
db.Configuration.LazyLoadingEnabled = false;
data = db.MyEntity.Local.ToBindingList();
Prevent Showing
Decorate the navigation property by [Browsable(false)]
.
[Browsable(false)]
public virtual EQUIPMENT EQUIPMENT { get; set; }
Or
Set the column visibility to false:
dataGridView1.Columns["EQUIPMENT"].Visible = false
Or
At the first place, define just a set of columns which you want for your DataGridView
using designer or using code. Then DataGridView
will show just those columns which you defined:
var DRIVERIDColumn = new DataGridViewTextBoxColumn();
DRIVERIDColumn.Name = "DRIVERID";
DRIVERIDColumn.HeaderText = "Id";
DRIVERIDColumn.DataPropertyName= "DRIVERID";
// ...
dataGridView1.Columns.AddRange(DRIVERIDColumn /*...*/);
// ...
Related Topics
Ienumerable and Recursion Using Yield Return
How to Serialize a C# Anonymous Type to a JSON String
Why Doesn't 'Ref' and 'Out' Support Polymorphism
How to Read ASP.NET Core Response.Body
Inheritance from Multiple Interfaces with the Same Method Name
Is Task.Run Considered Bad Practice in an Asp .Net MVC Web Application
String Concatenation Using '+' Operator
Has Foreach's Use of Variables Been Changed in C# 5
Replace First Occurrence of Pattern in a String
Deserialize Xml to Object Using Dynamic
"A Lambda Expression with a Statement Body Cannot Be Converted to an Expression Tree"
Embedding an External Executable Inside a C# Program