2017-08-17 51 views
0

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

我可能本周末:)

+0

为什么你需要这样的功能。刚刚编写'db.users.FirstOrDefault(function(x)x.Id = id)'比'new DataClass(of Users)'更容易吗?找到(id,db)' – Magnus

+0

如果你看看问题的背景下,我的主键不是全部命名为ID(可悲)。 –

+0

因为它是一个泛型类,所以想法是消费类只有一个保存和一个专用的查找以供私有更新方法使用。 –

回答

2

转换为C#要创建通用的查找,你需要建立整个拉姆达为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 

顺便说一句,它出现在我的数据库中,列是Field s,而不是Property s。

+0

当我开始工作时,我会试试这个。我增加了复杂性,使类是通用:) –

+0

哦,谢谢你的回答!我会回来后结果:) –

+0

这完美的作品。所有的测试都是绿色的,性能很好。 –