Linq to Sql问题中的自定义函数 - 我已经搜索了大约一个小时,现在试图找到这个问题的答案。然而,我为自己设定的难题比平常要困难一些。 在文章的底部是我在遗留项目中使用的通用CRUD操作类。我们可以纯粹使用静态方法,或者通过对所讨论的部分类具有适当的实例属性来使用它。 该项目使用DBML文件,因此这些部分用于此模型中的每个表。 我希望实现一个通用的Find(id,byref Db as dbcontext),以便类不需要它们自己的实现。Linq to Sql中的自定义.net函数
但是,我遇到了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(的T,布尔)),但给它一个,它呜呜。
我已经尝试了几种不同的方法来构建这个,但没有任何工作。我不想完全抛弃它,而是想知道是否有其他人解决了这个问题,如果使用其他库,我仍然可以得到。我见过托马斯佩特里切克的文章,但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 to sql)文件,并且在大中型传统项目的表单背面粘贴了很多数据库上下文,那么这将有所帮助。我使用t4模板和一个单独的使用反射来生成消费类的linq pad工具。如果有人对此感兴趣,我会将其添加到更大的文章中。
https://gist.github.com/SoulFireMage/cc725e4ff0e8b5af5ce6c38eb1fe2578
我可能本周末:)
为什么你需要这样的功能。刚刚编写'db.users.FirstOrDefault(function(x)x.Id = id)'比'new DataClass(of Users)'更容易吗?找到(id,db)' – Magnus
如果你看看问题的背景下,我的主键不是全部命名为ID(可悲)。 –
因为它是一个泛型类,所以想法是消费类只有一个保存和一个专用的查找以供私有更新方法使用。 –