Winforms 在DataGridView中显示导航属性的属性(二级属性)

Winforms 在DataGridView中显示导航属性的属性(二级属性),winforms,c#,.net,entity-framework,datagridview,Winforms,C#,.net,Entity Framework,Datagridview,我试图在应用程序中的DataGridView上显示相关实体的几个属性。这对我来说似乎很普通,但我很难找到例子。这是一个订单输入操作。订单数据、订单ID和提货日期,然后是网格中的行项目(以下模型中的OrderSheetItems)。订单行项目具有基于ProductId的导航属性Product。我可以使用DataGridViewComboBoxColumn,ProductId作为ValueMember,另一个字段作为DisplayMember。但我想在其他列中包括更多数据,如大小、颜色、材质等

我试图在应用程序中的
DataGridView
上显示相关实体的几个属性。这对我来说似乎很普通,但我很难找到例子。这是一个订单输入操作。订单数据、订单ID和提货日期,然后是网格中的行项目(以下模型中的OrderSheetItems)。订单行项目具有基于ProductId的导航属性Product。我可以使用DataGridViewComboBoxColumn,ProductId作为ValueMember,另一个字段作为DisplayMember。但我想在其他列中包括更多数据,如大小、颜色、材质等

下面是加载数据的代码

try
{
    _context.OrderSheets.Include(o => o.OrderSheetItems.Select(i => i.Product)).Load();
    orderSheetBindingSource.DataSource = _context.OrderSheets.Local.ToBindingList();
}
catch (Exception ex)...
ProductId位于单独的列中,仅用于试验,稍后将作为组合框。 那么,有没有办法将其他列绑定到OrderSheetItem的Product navigation属性中的数据,或者我必须处理Product id上的CellValueChanged才能物理设置其他列中的数据?如果有一种方法可以绑定列,那么是通过OnLoad中的代码还是网格视图列设计器中的某个地方


TIA,Mike

您可以使用以下任一选项:

  • 使用
    DataGridViewComboxColumn
  • 向子实体分部类添加相应的属性
  • 使用
    Linq
  • 使用
    CellFormatting
    事件获取子属性绑定列的值
  • 通过重写
    ToString()
  • 使用自定义的
    TypeDescriptor
    启用与子属性的数据绑定
  • 选项1-使用DataGridViewComboxColumn

    用法:这种方法特别适用于希望控件保持可编辑状态的情况

    在这种方法中,您可以使用
    DataGridViewComboxColumn
    显示navigationn属性的任何字段。要在网格中显示导航属性的多个字段子属性,请使用多个绑定到同一导航属性并具有不同显示成员的
    DataGridViewComboxColumn

    在这种方法中,除了您的
    ProductId
    列之外,还要向网格中添加更多的
    DataGridViewComboxColumn
    ,然后对所有其他组合列执行以下设置:

    • 将它们的
      DataPropertyName
      设置为
      ProductId
    • 将它们的
      DataSource
      属性设置为与主
      ProductId
      列使用的数据源完全相同,例如
      productBindingSource
    • 将它们中的
      ValueMember
      设置为您为产品id列设置的相同值成员,它是产品表的键列。(
      ProductId
    • 将它们中的每一个设置为要显示的列,例如,将其中一个设置为Name。一个根据价格,一个根据尺寸。这样可以显示相关的实体字段
    • 将它们的
      ReadOnly
      属性设置为
      true
      。它使单元格为只读
    • 如果要使列成为只读,请将它们的
      DisplayStyle
      属性设置为
      Nothing
      。它删除了下拉样式
    如果要使
    ProductId
    保持可编辑状态,请将其
    DisplayStyle
    保持为
    DropDownButton
    。这样,当您使用组合框更改
    ProductId
    列的值时,当您离开该行并移动到下一行时,您将看到该行的其他单元格,显示所选产品的其他属性。另外,由于其他组合框列是只读的,并且没有组合框样式,因此用户无法更改它们的值,它们的行为仅类似于显示相关实体中其他属性的只读文本框列

    选项2-向子实体分部类添加相应的属性

    用法:当您不需要编辑值时,此方法非常有用

    在这种方法中,您可以在子实体分部类中定义属性,以返回父实体对应属性的值。例如,对于product name,在order item分部类中定义此属性:

    public string ProductName
    {
        get
        {
            if (this.Product != null)
                return this.Product.Name;
            else 
                return string.Empty;
        }
    }
    
    然后,您可以在选择订单项时简单地包括产品,并将网格列绑定到订单项的相应属性

    选项3-将查询形状设置为包含导航属性的属性

    用法:当您不需要编辑值时,此方法非常有用

    可以对查询进行形状设置,以包含导航属性的属性。您可以简单地使用匿名对象或视图模式,例如:

    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();       
    
    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);
        }
    }
    
    选项4-使用CellFormatting事件获取子属性绑定列的值

    用法:当您不需要编辑值时,此方法非常有用

    在这种方法中,您可以使用
    DataGridView
    CellFormatting
    事件。您只需根据列索引设置
    e.Value
    。例如:

    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();       
    
    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);
        }
    }
    
    可以使用不同的条件处理不同的列并显示不同的子特性

    此外,还可以使用反射使其更具动态性和可重用性。可以使用反射提取导航属性的子属性的值。为此,您应该创建列并将
    DataPropertyName
    设置为子属性,如
    Product.Name
    ,然后在
    CellFormatting
    事件中,使用反射获取列的值。以下是Antonio Bello关于这种方法的一篇好文章:

    选项5-通过覆盖
    ToString()

    用法:当您不需要编辑值时,此方法非常有用

    如果只想显示导航属性的一列,只需重写导航属性clas的方法
    ToString()
    
    private void CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
    {
        if (e.ColumnIndex < 0 || e.RowIndex < 0) return;
        var dg = (DataGridView)sender;
        var propertyName = dg.Columns[e.ColumnIndex].DataPropertyName;
        if (propertyName.Contains("."))
        {
            var dataObject = dg.Rows[e.RowIndex].DataBoundItem;
            e.Value = GetPropertyValue(dataObject, propertyName);
        }
    }
    
    private void CellParsing(object sender, DataGridViewCellParsingEventArgs e)
    {
        var dg = (DataGridView)sender;
        var propertyName = dg.Columns[e.ColumnIndex].DataPropertyName;
        if (propertyName.Contains("."))
        {
            var dataObject = dg.Rows[e.RowIndex].DataBoundItem;
            SetPropertyValue(dataObject, propertyName, e.Value);
        }
    }
    
    var categories = new List<Category>() {
        new Category{ Id= 1, Name = "C1"},
        new Category{ Id= 2, Name = "C2"}
    };
    var products = new List<Product>() {
        new Product(){ Id = 1, Name ="P1", Category = categories[0]},
        new Product(){ Id = 2, Name ="P2", Category = categories[0]},
        new Product(){ Id = 3, Name ="P3", Category = categories[1]},
    };
    var dg = new DataGridView();
    dg.AutoGenerateColumns = false;
    dg.Columns.Add(new DataGridViewTextBoxColumn()
    {
        HeaderText = "Id",
        DataPropertyName = "Id"
    });
    dg.Columns.Add(new DataGridViewTextBoxColumn()
    {
        HeaderText = "Name",
        DataPropertyName = "Name"
    });
    dg.Columns.Add(new DataGridViewTextBoxColumn()
    {
        HeaderText = "CategoryId",
        DataPropertyName = "Category.Id"
    });
    dg.Columns.Add(new DataGridViewTextBoxColumn()
    {
        HeaderText = "CategoryName",
        DataPropertyName = "Category.Name"
    });
    dg.Dock = DockStyle.Fill;
    dg.DataSource = products;
    this.Controls.Add(dg);
    dg.CellFormatting += CellFormatting;
    dg.CellParsing += CellParsing;