如何使用 Linq 查询创建连接 Entity Framework 表的可更新 DataGridView
How to make updatable DataGridView of joined Entity Framework tables using Linq query
我想使用 DataGridView 显示和更新来自两个连接的 MSSQL 表的数据,但我不知道如何显示我想要的数据 and 使其可更新。
在这种情况下,我想显示特定机器类型的物料清单(下面代码中的"HX")。 'Parts' 是独特的机器组件(因此包含库存水平、零件图等),而 'Items' 是 'instances' 特定上下文中的零件或机器内的装配,因此包含数量,该用途的说明等。
从 DB/model 的角度来看,这些表具有多(项目)对一(部分)的关系。
下面的代码显示了我能想到的所有 Linq 查询结构,none 其中满足了我需要的一切:
Public Class Form1
' Entity Framework members
Private context As New EJEFData.CorporateEntities
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
' This displays ALL 'Item' fields and can be updated via the DataGridView.
' However, it doesn't show any of the related 'Part' fields which I need
Dim data1 = From i In context.Items
Where i.Type = "HX"
Order By i.Item1
Select i
' This displays all the columns I want (and no more) and demonstrates the the magic of entity framework as the table join
' is all part of the model! However, the output is read only so can't change the data.
Dim data2 = From i In context.Items
Where i.Type = "HX"
Order By i.Item1
Select i.Part.Stock, i.Model, i.Item1, i.Part.DrawingType, i.Description, i.Part.Supplier, i.Part.SuppliersDescription, i.Qty, i.Status
' This displays just two columns on the DataGridView: text representations of the the particular 'Part' and 'Item' classes...
Dim data3 = From i In context.Items
Join p In context.Parts On i.PartID Equals p.ID
Where i.Type = "HX"
Order By i.Item1
Select i, p
' ...However, the underlying data is there and can be edited by uncommenting:
'Dim itemList = data3.ToList
'itemList(0).i.Description = InputBox("New Value:",, itemList(0).i.Description)
'm_context.SaveChanges()
' This seems to be another way of achieving data2 but with the join defined in the query rather than using the Entity Framework model structure
' Again, it is read only.
Dim data4 = From i In context.Items
Join p In context.Parts On i.PartID Equals p.ID
Where i.Type = "HX"
Order By i.Item1
Select p.Stock, i.Model, i.Item1, p.DrawingType, i.Description, p.Supplier, p.SuppliersDescription, i.Qty, i.Status
' This dispays an editable DataGridView (perfect!) BUT doesn't save the changes back to the database
Dim data5 = From i In context.Items
Where i.Type = "HX"
Order By i.Item1
Select New With {.Stock = i.Part.Stock, .Model = i.Model, .Item = i.Item1, .DrawingType = i.Part.DrawingType,
.Description = i.Description, .Supplier = i.Part.Supplier, .SuppliersDescription = i.Part.SuppliersDescription, .Qty = i.Qty, .Status = i.Status}
' Bind the data to the DataGridView via a BindingSource (data1 can be changed to data2, data3 etc.)
PartBindingSource.DataSource = data1.ToList
DataGridView1.DataSource = PartBindingSource
End Sub
' Update the database when leaving a modified DataGridView row
Private Sub DataGridView1_RowValidated(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.RowValidated
context.SaveChanges()
End Sub
End Class
我尝试了一种低级别的方法,使用一个数据集填充和更新自定义 SQL 查询,但在我的项目中使用它会非常耗时且容易出错。无论如何,根据 MSDN:
Entity Framework is Microsoft’s recommended data access technology for
new applications
但是,如果 Entity Framework 对于这个应用程序来说确实是错误的方法,那么我很高兴听到任何其他建议!
这是我试过的
public class DataBindingProjection
{
public string name { get; set; }
public int apmntid { get; set; }
public string servicedesc { get; set; }
public string cusid { get; set; }
public string empid { get; set;
public bool isdelivered { get; set; }
}
context = new HHCSEntities();
var query = from d in context.DeliveryOrders
join a in context.Appointments on d.Id equals a.Id
join s in context.ServiceCategories on d.ServiceId equals s.ServiceId
join e in context.Employees on d.EmpId equals e.EmpId
select new DataBindingProjection
{
dono = d.Name,
apmntid = a.Id,
servicedesc = s.ServiceDesc,
cusid = a.CusId,
empid = d.EmpId,
shortname = e.ShrtName,
isdelivered = d.IsDelivered
};
dataGridView1.DataSource = query.ToList();
dataGridView1.Columns[1].DataPropertyName = "Name";
dataGridView1.Columns[2].DataPropertyName = "Id";
dataGridView1.Columns[3].DataPropertyName = "servicedesc";
dataGridView1.Columns[4].DataPropertyName = "apmntid";
dataGridView1.Columns[5].DataPropertyName = "empid";
dataGridView1.Columns[9].DataPropertyName = "isdelivered";
您可以使用这种方式来更新DateGrideView Table
BindingSource bi = new BindingSource();
bi.DataSource = query.ToList();
dataGridView1.DataSource = bi;
dataGridView1.Refresh();
最后我使用了问题中的 'Data1' 方法,但是创建了一个继承的 DataGridView class 可以接受列绑定中的嵌套属性(例如 Part.Stock 以获取 Item .Part.Stock).但是,这意味着无法自动生成这些列,因为没有自动检测需要哪些嵌套属性的机制。
请参阅下面的 DataGridView 代码(我已经删除了一些有问题的错误检查,所以如果它看起来不完整,那可能就是原因)。
这基本上是通过读取列绑定路径以及它是否包含“。”来工作的。使用反射获取或设置 属性。
Imports System.Reflection
Imports System.Linq.Dynamic
Public Class NestedSourceGrid
' Inherits DataGridView in designer generated code
Private _commitAttempt As Boolean
''' <summary>
''' Stores list of properties for nested property bound columns
''' Key = column name
''' </summary>
Dim BindPropertyLists As New Dictionary(Of String, List(Of String))
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
DoubleBuffered = True
End Sub
Private Function GetBindProperty(ByVal [property] As Object, ByVal propertyName As String) As Object
Dim retValue As Object = Nothing
If propertyName.Contains(".") Then
Dim thisPropertyName As String
thisPropertyName = propertyName.Substring(0, propertyName.IndexOf("."))
propertyName = propertyName.Substring(propertyName.IndexOf(".") + 1)
retValue = GetBindProperty([property].GetType().GetProperty(thisPropertyName).GetValue([property], Nothing), propertyName)
Else
' Return this (bottom) property value
Dim tempValue = [property].GetType().GetProperty(propertyName).GetValue([property], Nothing)
If tempValue Is Nothing Then Return ""
retValue = tempValue
End If
Return retValue
End Function
Private Sub SetBindProperty(ByVal [property] As Object, ByVal propertyName As String, ByVal value As Object)
' If there are further property nest levels
If propertyName.Contains(".") Then
Dim thisPropertyName As String
thisPropertyName = propertyName.Substring(0, propertyName.IndexOf("."))
propertyName = propertyName.Substring(propertyName.IndexOf(".") + 1)
SetBindProperty([property].GetType().GetProperty(thisPropertyName).GetValue([property], Nothing), propertyName, value)
Else ' Bottom property level
' Set the value to this (bottom) property
[property].GetType().GetProperty(propertyName).SetValue([property], value)
End If
End Sub
Private Function GetBindPropertyType(ByVal propertyType As Type, ByVal propertyName As String, ByVal columnName As String) As Type
Dim retValue As Type = Nothing
' If there are further property nest levels
If propertyName.Contains(".") Then
Dim thisPropertyName As String
thisPropertyName = propertyName.Substring(0, propertyName.IndexOf("."))
propertyName = propertyName.Substring(propertyName.IndexOf(".") + 1)
retValue = GetBindPropertyType(propertyType.GetProperty(thisPropertyName).PropertyType, propertyName, columnName)
Else ' Bottom property level
Dim propertyInfo As PropertyInfo
propertyInfo = propertyType.GetProperty(propertyName)
retValue = propertyInfo.PropertyType
End If
Return retValue
End Function
Private Sub MultiSourceGrid_RowsAdded(sender As Object, e As DataGridViewRowsAddedEventArgs) Handles Me.RowsAdded
If Rows(e.RowIndex).DataBoundItem Is Nothing Then Exit Sub
' Clear the BindPropertyLists so Add can't throw exception
BindPropertyLists.Clear()
For Each col As DataGridViewColumn In Columns
If col.DataPropertyName.Contains(".") Then
' Create new (empty) list for this column in BindPropertyLists
BindPropertyLists.Add(col.Name, New List(Of String))
col.ValueType = GetBindPropertyType(Rows(0).DataBoundItem.GetType, col.DataPropertyName, col.Name)
' Insert the values from 'complex bound' source objects
For i As Integer = e.RowIndex To e.RowIndex + e.RowCount - 1
Rows.Item(i).Cells.Item(col.Index).Value = GetBindProperty(Rows(i).DataBoundItem, col.DataPropertyName)
Next
End If
Next
End Sub
Private Sub MultiSourceGrid_CellValidating(sender As Object, e As DataGridViewCellValidatingEventArgs) Handles Me.CellValidating
' If this is an 'complex bound' column, attempt to set the value to the source object
If (Columns(e.ColumnIndex).DataPropertyName.Contains(".")) And e.RowIndex >= 0 And e.ColumnIndex >= 0 Then
Try
_commitAttempt = True
CommitEdit(DataGridViewDataErrorContexts.Formatting) 'DataGridViewDataErrorContexts.Commit Or DataGridViewDataErrorContexts.Parsing)
Catch ex As Exception
MsgBox("[during cell validating]" + vbNewLine + ex.Message)
e.Cancel = True
End Try
_commitAttempt = False
End If
End Sub
Private Sub MultiSourceGrid_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles Me.CellValueChanged
' If this is an 'complex bound' column, attempt to set the value to the source object
If _commitAttempt Then
Dim cell As DataGridViewCell = Rows.Item(e.RowIndex).Cells.Item(e.ColumnIndex)
SetBindProperty(Rows.Item(e.RowIndex).DataBoundItem, Columns.Item(e.ColumnIndex).DataPropertyName, cell.Value)
End If
End Sub
End Class
我想使用 DataGridView 显示和更新来自两个连接的 MSSQL 表的数据,但我不知道如何显示我想要的数据 and 使其可更新。
在这种情况下,我想显示特定机器类型的物料清单(下面代码中的"HX")。 'Parts' 是独特的机器组件(因此包含库存水平、零件图等),而 'Items' 是 'instances' 特定上下文中的零件或机器内的装配,因此包含数量,该用途的说明等。
从 DB/model 的角度来看,这些表具有多(项目)对一(部分)的关系。
下面的代码显示了我能想到的所有 Linq 查询结构,none 其中满足了我需要的一切:
Public Class Form1
' Entity Framework members
Private context As New EJEFData.CorporateEntities
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
' This displays ALL 'Item' fields and can be updated via the DataGridView.
' However, it doesn't show any of the related 'Part' fields which I need
Dim data1 = From i In context.Items
Where i.Type = "HX"
Order By i.Item1
Select i
' This displays all the columns I want (and no more) and demonstrates the the magic of entity framework as the table join
' is all part of the model! However, the output is read only so can't change the data.
Dim data2 = From i In context.Items
Where i.Type = "HX"
Order By i.Item1
Select i.Part.Stock, i.Model, i.Item1, i.Part.DrawingType, i.Description, i.Part.Supplier, i.Part.SuppliersDescription, i.Qty, i.Status
' This displays just two columns on the DataGridView: text representations of the the particular 'Part' and 'Item' classes...
Dim data3 = From i In context.Items
Join p In context.Parts On i.PartID Equals p.ID
Where i.Type = "HX"
Order By i.Item1
Select i, p
' ...However, the underlying data is there and can be edited by uncommenting:
'Dim itemList = data3.ToList
'itemList(0).i.Description = InputBox("New Value:",, itemList(0).i.Description)
'm_context.SaveChanges()
' This seems to be another way of achieving data2 but with the join defined in the query rather than using the Entity Framework model structure
' Again, it is read only.
Dim data4 = From i In context.Items
Join p In context.Parts On i.PartID Equals p.ID
Where i.Type = "HX"
Order By i.Item1
Select p.Stock, i.Model, i.Item1, p.DrawingType, i.Description, p.Supplier, p.SuppliersDescription, i.Qty, i.Status
' This dispays an editable DataGridView (perfect!) BUT doesn't save the changes back to the database
Dim data5 = From i In context.Items
Where i.Type = "HX"
Order By i.Item1
Select New With {.Stock = i.Part.Stock, .Model = i.Model, .Item = i.Item1, .DrawingType = i.Part.DrawingType,
.Description = i.Description, .Supplier = i.Part.Supplier, .SuppliersDescription = i.Part.SuppliersDescription, .Qty = i.Qty, .Status = i.Status}
' Bind the data to the DataGridView via a BindingSource (data1 can be changed to data2, data3 etc.)
PartBindingSource.DataSource = data1.ToList
DataGridView1.DataSource = PartBindingSource
End Sub
' Update the database when leaving a modified DataGridView row
Private Sub DataGridView1_RowValidated(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.RowValidated
context.SaveChanges()
End Sub
End Class
我尝试了一种低级别的方法,使用一个数据集填充和更新自定义 SQL 查询,但在我的项目中使用它会非常耗时且容易出错。无论如何,根据 MSDN:
Entity Framework is Microsoft’s recommended data access technology for new applications
但是,如果 Entity Framework 对于这个应用程序来说确实是错误的方法,那么我很高兴听到任何其他建议!
这是我试过的
public class DataBindingProjection
{
public string name { get; set; }
public int apmntid { get; set; }
public string servicedesc { get; set; }
public string cusid { get; set; }
public string empid { get; set;
public bool isdelivered { get; set; }
}
context = new HHCSEntities();
var query = from d in context.DeliveryOrders
join a in context.Appointments on d.Id equals a.Id
join s in context.ServiceCategories on d.ServiceId equals s.ServiceId
join e in context.Employees on d.EmpId equals e.EmpId
select new DataBindingProjection
{
dono = d.Name,
apmntid = a.Id,
servicedesc = s.ServiceDesc,
cusid = a.CusId,
empid = d.EmpId,
shortname = e.ShrtName,
isdelivered = d.IsDelivered
};
dataGridView1.DataSource = query.ToList();
dataGridView1.Columns[1].DataPropertyName = "Name";
dataGridView1.Columns[2].DataPropertyName = "Id";
dataGridView1.Columns[3].DataPropertyName = "servicedesc";
dataGridView1.Columns[4].DataPropertyName = "apmntid";
dataGridView1.Columns[5].DataPropertyName = "empid";
dataGridView1.Columns[9].DataPropertyName = "isdelivered";
您可以使用这种方式来更新DateGrideView Table
BindingSource bi = new BindingSource();
bi.DataSource = query.ToList();
dataGridView1.DataSource = bi;
dataGridView1.Refresh();
最后我使用了问题中的 'Data1' 方法,但是创建了一个继承的 DataGridView class 可以接受列绑定中的嵌套属性(例如 Part.Stock 以获取 Item .Part.Stock).但是,这意味着无法自动生成这些列,因为没有自动检测需要哪些嵌套属性的机制。 请参阅下面的 DataGridView 代码(我已经删除了一些有问题的错误检查,所以如果它看起来不完整,那可能就是原因)。 这基本上是通过读取列绑定路径以及它是否包含“。”来工作的。使用反射获取或设置 属性。
Imports System.Reflection
Imports System.Linq.Dynamic
Public Class NestedSourceGrid
' Inherits DataGridView in designer generated code
Private _commitAttempt As Boolean
''' <summary>
''' Stores list of properties for nested property bound columns
''' Key = column name
''' </summary>
Dim BindPropertyLists As New Dictionary(Of String, List(Of String))
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
DoubleBuffered = True
End Sub
Private Function GetBindProperty(ByVal [property] As Object, ByVal propertyName As String) As Object
Dim retValue As Object = Nothing
If propertyName.Contains(".") Then
Dim thisPropertyName As String
thisPropertyName = propertyName.Substring(0, propertyName.IndexOf("."))
propertyName = propertyName.Substring(propertyName.IndexOf(".") + 1)
retValue = GetBindProperty([property].GetType().GetProperty(thisPropertyName).GetValue([property], Nothing), propertyName)
Else
' Return this (bottom) property value
Dim tempValue = [property].GetType().GetProperty(propertyName).GetValue([property], Nothing)
If tempValue Is Nothing Then Return ""
retValue = tempValue
End If
Return retValue
End Function
Private Sub SetBindProperty(ByVal [property] As Object, ByVal propertyName As String, ByVal value As Object)
' If there are further property nest levels
If propertyName.Contains(".") Then
Dim thisPropertyName As String
thisPropertyName = propertyName.Substring(0, propertyName.IndexOf("."))
propertyName = propertyName.Substring(propertyName.IndexOf(".") + 1)
SetBindProperty([property].GetType().GetProperty(thisPropertyName).GetValue([property], Nothing), propertyName, value)
Else ' Bottom property level
' Set the value to this (bottom) property
[property].GetType().GetProperty(propertyName).SetValue([property], value)
End If
End Sub
Private Function GetBindPropertyType(ByVal propertyType As Type, ByVal propertyName As String, ByVal columnName As String) As Type
Dim retValue As Type = Nothing
' If there are further property nest levels
If propertyName.Contains(".") Then
Dim thisPropertyName As String
thisPropertyName = propertyName.Substring(0, propertyName.IndexOf("."))
propertyName = propertyName.Substring(propertyName.IndexOf(".") + 1)
retValue = GetBindPropertyType(propertyType.GetProperty(thisPropertyName).PropertyType, propertyName, columnName)
Else ' Bottom property level
Dim propertyInfo As PropertyInfo
propertyInfo = propertyType.GetProperty(propertyName)
retValue = propertyInfo.PropertyType
End If
Return retValue
End Function
Private Sub MultiSourceGrid_RowsAdded(sender As Object, e As DataGridViewRowsAddedEventArgs) Handles Me.RowsAdded
If Rows(e.RowIndex).DataBoundItem Is Nothing Then Exit Sub
' Clear the BindPropertyLists so Add can't throw exception
BindPropertyLists.Clear()
For Each col As DataGridViewColumn In Columns
If col.DataPropertyName.Contains(".") Then
' Create new (empty) list for this column in BindPropertyLists
BindPropertyLists.Add(col.Name, New List(Of String))
col.ValueType = GetBindPropertyType(Rows(0).DataBoundItem.GetType, col.DataPropertyName, col.Name)
' Insert the values from 'complex bound' source objects
For i As Integer = e.RowIndex To e.RowIndex + e.RowCount - 1
Rows.Item(i).Cells.Item(col.Index).Value = GetBindProperty(Rows(i).DataBoundItem, col.DataPropertyName)
Next
End If
Next
End Sub
Private Sub MultiSourceGrid_CellValidating(sender As Object, e As DataGridViewCellValidatingEventArgs) Handles Me.CellValidating
' If this is an 'complex bound' column, attempt to set the value to the source object
If (Columns(e.ColumnIndex).DataPropertyName.Contains(".")) And e.RowIndex >= 0 And e.ColumnIndex >= 0 Then
Try
_commitAttempt = True
CommitEdit(DataGridViewDataErrorContexts.Formatting) 'DataGridViewDataErrorContexts.Commit Or DataGridViewDataErrorContexts.Parsing)
Catch ex As Exception
MsgBox("[during cell validating]" + vbNewLine + ex.Message)
e.Cancel = True
End Try
_commitAttempt = False
End If
End Sub
Private Sub MultiSourceGrid_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles Me.CellValueChanged
' If this is an 'complex bound' column, attempt to set the value to the source object
If _commitAttempt Then
Dim cell As DataGridViewCell = Rows.Item(e.RowIndex).Cells.Item(e.ColumnIndex)
SetBindProperty(Rows.Item(e.RowIndex).DataBoundItem, Columns.Item(e.ColumnIndex).DataPropertyName, cell.Value)
End If
End Sub
End Class