Linq to SQL 中的自定义 .net 函数



Linq to Sql 问题中的自定义函数 - 我已经用谷歌搜索了大约一个小时,试图找到这个问题的答案。但是,我为自己设置的难题比平时要棘手一些。 帖子的底部是我在旧项目中使用的通用 CRUD 操作类。我们可以纯粹使用静态方法使用它,也可以通过在所讨论的分部类上具有适当的实例属性来使用它。 该项目使用 DBML 文件,因此这些部件适用于此模型中的每个表。 我希望实现一个通用的 Find(id, byref Db 作为 dbcontext(,这样类就不需要自己的实现。

但是,我遇到了Linq-to-Sql在将.net函数转换为SQL查询方面相当令人沮丧的迟钝。

我已经尝试了几种方法 - 构建表达式树等,但没有任何东西通过单元测试,我最终得到相同的错误消息:

消息:System.NotSupportedException:方法"System.Object DynamicInvoke(System.Object[]("不支持转换为SQL。

这就是我现在所处的位置:

Friend Function Find(Id As Integer, ByRef db As Travelworld_DataContext) As T
Dim table = DirectCast(db.GetTable(M_Type), Linq.Table(Of T))
If Id < 1 Then Return Nothing
Dim x As Expression(Of Func(Of T, Boolean)) = Function(c) CInt(PrimaryKeyProperty.GetValue(c)) = Id
Return table.Where(Function(c As T) x.Compile.Invoke(c)).FirstOrDefault
End Function

如果我只是尝试将表达式本身放在这里,如下所示:

Return table.Where(Function(c As T) x(c)).FirstOrDefault

我得到 x 无法被索引,因为它没有默认值——我在这里的想法是它想要一个表达式(的 Func(of T,布尔值((,但给它一个,它会发牢骚。

我已经尝试了几种不同的方法来构建它,但还没有奏效。我不想完全放弃它,而是想看看是否有其他人已经解决了这个问题,如果使用其他库,我仍然可以获得。我看过Tomas Petricek的文章,但Dlinq项目不再出现。

下面的代码是完整的类减去我们使用的几个额外方法。如果其他人需要这个,它将适用于处于类似情况的其他人。

由于架构位于旧项目(大型项目(中,因此我们无法更改主键名称和其他项,因此我们必须以相当手动的方式选取它们。

谢谢你的任何建议!

Public Class DataClass(Of T As Class)
#Region "Shared"
Private Shared DoNotUpdate As List(Of String) = New List(Of String) From {"CreatedOn", "CreatedBy", "Created_On", "Created_By", "Create_On", "Create_by", "CreateBy", "CreateOn"}
Private Shared Function ValidProperties(p As Type, NonWriteable() As String) As List(Of PropertyInfo)
DoNotUpdate.AddRange(NonWriteable)
Return p.GetProperties.Where(Function(x) Not x.PropertyType.Name.Contains("EntityRef") _
AndAlso Not DoNotUpdate.Any(Function(s) s.ToUpper.Contains(x.Name.ToUpper)) _
AndAlso x.CanWrite AndAlso x.PropertyType.Namespace = "System").ToList
End Function
''' <summary>
''' Send me db, UpdateItem, OriginalItem and array of field you want me to skip and I'll compare and update the rest.
''' Example: Return DataClass(Of Part).Update(db, updated, Original, {"Part_id"} )
''' </summary>
''' <param name="db"></param>
''' <param name="UpdateItem"></param>
''' <param name="originalItem"></param>
''' <param name="NonWriteable"></param> 
''' <returns></returns>
Public Shared Function Update(ByRef db As DBContext,
UpdateItem As T,
originalItem As T,
NonWriteable() As String,
Optional _validProperties As List(Of PropertyInfo) = Nothing) As T
If originalItem Is Nothing OrElse UpdateItem Is Nothing Then Return Nothing
If _validProperties Is Nothing Then _validProperties = ValidProperties(UpdateItem.GetType, NonWriteable)
For Each p In _validProperties
If p.PropertyType.Name.Contains("Nullable") Then
If Not Nullable.Equals(p.GetValue(UpdateItem), p.GetValue(originalItem)) Then p.SetValue(originalItem, p.GetValue(UpdateItem))
Else
If p.GetValue(UpdateItem) IsNot p.GetValue(originalItem) Then p.SetValue(originalItem, p.GetValue(UpdateItem))
End If
Next 
If db.GetChangeSet.Updates.Count > 0 Then SubmitChanges(db)
Return originalItem
End Function
''' <summary>
''' Example  Return If(Item IsNot Nothing, DataClass(Of Part).Save(Item.part_id, Item, Function() Update(Item)), Nothing)
''' </summary>
''' <param name="PrimaryKey"></param>
''' <param name="UpdateItem"></param>
''' <param name="updateAction"></param>
''' <returns></returns>
Public Shared Function Save(PrimaryKey As Integer,
UpdateItem As T,
updateAction As Func(Of T),
Optional RefreshAction As Action = Nothing) As T
If UpdateItem Is Nothing Then Return Nothing
If PrimaryKey = 0 Then 'Without an interface or inheritance we can't know which field is primary key without extensive reflection.
Using db As New DBContext
db.DeferredLoadingEnabled = False
Dim table = db.GetTable(UpdateItem.GetType) 'What table are we inserting to?
table.InsertOnSubmit(UpdateItem)
SubmitChanges(db)
End Using
Else
UpdateItem = updateAction() 'Need a proper update action sent through 
End If
If RefreshAction IsNot Nothing Then RefreshAction()
Return UpdateItem
End Function
#End Region
#Region "Instance Version"
Private ReadOnly M_Type As Type
Private ReadOnly PrimayKeyName As String
Private ReadOnly PrimaryKeyProperty As PropertyInfo
Private _ValidProperties As List(Of PropertyInfo)
Private ReadOnly Property M_ValidProperties As List(Of PropertyInfo)
Get
Return _ValidProperties
End Get
End Property
Public Sub New(_type As Type, pkey As String)
M_Type = _type
PrimayKeyName = If(pkey <> "", pkey, "id")
PrimaryKeyProperty = _type.GetProperties.Where(Function(x) x.Name = pkey).FirstOrDefault
_ValidProperties = ValidProperties(_type, {pkey})
End Sub
Public Function Save(Item As T) As T
Return If(Item IsNot Nothing, Save(CInt(PrimaryKeyProperty.GetValue(Item)), Item, Function() Update(Item)), Nothing)
End Function
Private Function Update(Updated As T) As T
Using db As New DBContext
Dim Original = Find(CInt(PrimaryKeyProperty.GetValue(Updated)), db)
Return Update(db, Updated, Original, {PrimayKeyName}, M_ValidProperties)
End Using
End Function
Public Function Clone(Original As T) As T
Dim c = Clone(Original, {PrimayKeyName})
Save(c)
Return c
End Function

结束类

更新:感谢Netmage,我有一个完整的通用数据层类,可以在商业环境中工作。

减去任何机密,我在这里为有类似情况的任何其他人提供了一个完整的版本。

如果您在Winforms中使用DBML(linq到sql(文件,并且在中型到大型遗留项目中将大量数据库上下文工作卡在表单背面,那么这将有所帮助。我使用 t4 模板和一个单独的 linq pad 工具,该工具使用反射来生成消费类。如果有人感兴趣,我会将其添加到更大的文章中。

https://gist.github.com/SoulFireMage/cc725e4ff0e8b5af5ce6c38eb1fe2578

我可能会在本周末将其转换为 C# :)

要创建通用 Find,您需要将整个 lambda 构建为Expression树,而不仅仅是最终结果:

Friend Function Find(Id As Integer, ByRef db As Travelworld_DataContext) As T
Dim table = DirectCast(db.GetTable(M_Type), Linq.Table(Of T))
If Id < 1 Then Return Nothing
Dim parm = Expression.Parameter(GetType(T))
Dim keyRef = Expression.PropertyOrField(parm, PrimaryKeyProperty.Name)
Dim keyMatch = Expression.Constant(Id)
Dim Body = Expression.MakeBinary(ExpressionType.Equal, keyRef, keyMatch)
Dim x As Expression(Of Func(Of Accounts, Boolean)) = Expression.Lambda(Body, parm)
Return table.Where(x).FirstOrDefault
End Function

顺便说一句,在我的数据库中,列是Fields,而不是Propertys。

最新更新