我正在使用 WPF + Linq to SQL 做一个 LOB 应用程序,而 Linq-To-Sql 集合未正确实现 INotifyCollectionChanged 的问题是我必须在系统的各个方面解决的问题。
到目前为止,我发现的最佳解决方案是执行以下任一操作:
- 在您的 DataContext 类上创建一个 model 层,以便您的 GUI 代码仅与模型层交互,而不直接与 DataContext 交互。在您的业务逻辑方法中,始终将返回的集合包装在 ObservableCollection 中
和/或
- 在您的实体类上实现 secondary 集合属性,这样您原来有 Customer.Products 的地方现在有 *Customer.Products_Observable",其中这个新的只读属性只返回一个 ObservableCollection,它包装了 Customer.Products 返回的任何内容。
和/或
- 创建一个派生自 ObservableCollection 的新类,该类可识别 DataContext。如果您重写此类的 Add/Insert/Remove 方法,则对集合的任何更改都可以自动传播到 DataContext 和 InsertOnSubmit / DeleteOnSubmit 调用。
以下是此类的一个示例:
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Linq
Imports System.Data.Linq
Public Class ObservableEntityCollection(Of T As {Class})
Inherits ObservableCollection(Of T)
Private _Table As Table(Of T)
Public Sub New(ByVal Context As DataContext)
Me._Table = Context.GetTable(Of T)()
End Sub
Public Sub New(ByVal Context As DataContext, ByVal items As IEnumerable(Of T))
MyBase.New(items)
Me._Table = Context.GetTable(Of T)()
End Sub
Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As T)
_Table.InsertOnSubmit(item)
MyBase.InsertItem(index, item)
End Sub
Public Shadows Sub Add(ByVal item As T)
_Table.InsertOnSubmit(item)
MyBase.Add(item)
End Sub
Public Shadows Sub Remove(ByVal item As T)
If MyBase.Remove(item) Then
_Table.DeleteOnSubmit(item)
End If
Dim deletable As IDeletableEntity = TryCast(item, IDeletableEntity)
If deletable IsNot Nothing Then deletable.OnDelete()
End Sub
Protected Overrides Sub RemoveItem(ByVal index As Integer)
Dim Item As T = Me(index)
_Table.DeleteOnSubmit(Item)
MyBase.RemoveItem(index)
End Sub
End Class
Public Interface IDeletableEntity
Sub OnDelete()
End Interface
IDeletable 接口允许您在实体类上实现特定逻辑(如清理外键和删除子对象)。
请注意,该类需要一个 DataContext 引用作为构造函数,这使其非常适合与上述场景 1) 一起使用(即从模型层/类中使用)。如果您想实现方法 2) [即在实体上作为属性],那么您可以赋予附加实体“找到”其 DataContext 的能力,如下所示:
[关于实体类:]
Public Property Context() As DataContext
Get
If _context Is Nothing Then
_context = DataContextHelper.FindContextFor(Me)
Debug.Assert(_context IsNot Nothing, "This object has been disconnected from it's DataContext, and cannot perform the requeted operation.")
End If
Return _context
End Get
Set(ByVal value As DataContext)
_context = value
End Set
End Property
Private _context As DataContext
[作为实用类]:
Public NotInheritable Class DataContextHelper
Private Const StandardChangeTrackerName As String = "System.Data.Linq.ChangeTracker+StandardChangeTracker"
Public Shared Function FindContextFor(ByVal this as DataContext, ByVal caller As Object) As JFDataContext
Dim hasContext As Boolean = False
Dim myType As Type = caller.GetType()
Dim propertyChangingField As FieldInfo = myType.GetField("PropertyChangingEvent", BindingFlags.NonPublic Or BindingFlags.Instance)
Dim propertyChangingDelegate As PropertyChangingEventHandler = propertyChangingField.GetValue(caller)
Dim delegateType As Type = Nothing
For Each thisDelegate In propertyChangingDelegate.GetInvocationList()
delegateType = thisDelegate.Target.GetType()
If delegateType.FullName.Equals(StandardChangeTrackerName) Then
propertyChangingDelegate = thisDelegate
Dim targetField = propertyChangingDelegate.Target
Dim servicesField As FieldInfo = targetField.GetType().GetField("services", BindingFlags.NonPublic Or BindingFlags.Instance)
If servicesField IsNot Nothing Then
Dim servicesObject = servicesField.GetValue(targetField)
Dim contextField As FieldInfo = servicesObject.GetType.GetField("context", BindingFlags.NonPublic Or BindingFlags.Instance)
Return contextField.GetValue(servicesObject)
End If
End If
Next
Return Nothing
End Function
注意:实体只有在打开 ChangeTracking 的情况下附加到 DataContext 时才能找到它的 DataContext。上面的 hack(是的 - 这是一个 hack!)依赖于它。