Show Properties of a Navigation Property in Datagridview (Second Level Properties)

Show Properties of a Navigation Property in DataGridView (Second Level Properties)

You can use either of these options:

  1. Use DataGridViewComboBoxColumn
  2. Add corresponding properties to child entity partial class
  3. Shape the query to include properties of navigation property using Linq
  4. Use CellFormatting event to get value for sub property bounded columns
  5. Show string representation of object by overriding ToString()
  6. 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 to ProductId
  • Set the DataSource property of them, to exactly the same data source you used for main ProductId column, for example productBindingSource
  • 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 to true. It makes the cell read only.
  • If you want to make columns readonly Set DisplayStyle property of them to Nothing. 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:

  1. Use a DataGridViewComboBox column and bind it to CompanyId and set it's DataSource to list of companies, and DisplayMember property to Name property of company and ValueMember to Id property of company.

  2. Override ToString() method of Company class and return Name of company. Then show Company navigation property in grid.

  3. Create a CompanyName property for your Estimate which returns its Company.Name value and show CompanyName in grid.

  4. Using CellFormatting event of DataGridView and set e.Value to desired value (company name) you want to display in cell.

  5. Shape your Estimates list using a Linq query or use a ViewModel and pass the result to data grid view.

  6. Create a TypeDescriptor for your Estimate type to resolve second level properties.
    .
    To show a property of company instead of company id, you can use a DataGridViewComboBoxColumn.

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 of Estimates
  • The DataGridView should bind to EstimatesBindingSource
  • CompanyBindingSource is only used as data source of the combo box column and should be filled using a list of Companies
  • To show CompanyName in Estimates list, it's enough to use a DataGridViewComboBoxColumn and set it's DataSource to list of companies and set the DisplayMember to CompanyName and it's value member to Id. And bind it to CompanyId field of Estimate.

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



Leave a reply



Submit