如何以适合ORM的方式在.NET中存储可扩展的元数据?

12

看起来我找不到任何有关于“如何在ORM工具中使用EAV方法”的答案,所以我想在这里试试手气。

假设我有一个Entities表:

ID -> int
Name -> nvarchar(50)

一张Images表:

EntityID -> int
Width -> int
Height -> int

还有一个Songs表:

EntityID -> int
Duration -> decimal(12,3)

我需要向实体添加可扩展的元数据(带有类型信息的未知键值对),以便能够发出如下查询:

找到所有持续时间超过3分钟,并且名称以“The”开头,具有满足以下标准的元数据的歌曲:

  • HasGuitarSolo设置为true
  • GuitarSoloDuration大于30秒

并按照GuitarSoloDuration降序排序结果。

我不想在数据库中创建HasGuitarSoloGuitarSoloDuration等列,理想情况下我想将它们存储在类似EAV的模式中,或者是一种不需要提前知道键的替代方案。

4个回答

3
在表中添加一个名为“metadata”的列,并将XML放入其中。 SQL服务器允许您查看一个充满XML的BLOB,就像它是附加列一样(有限制)。
对于ORM,它取决于您的对象结构如何。
无限定制的元数据项:将XML中的名称-值对放入集合中。如果您的ORM不允许此操作,请直接放入字符串属性中,setter可以将其解析为XML文档(或更快的对象,如果需要速度)。 Getter将返回字符串。然后,一个不是ORM'd的单独属性'MetaDataItem(ItemName-> string)'将从元数据列表中读取值,并使用其setter更新/添加它们。
Metadeta是硬编码属性 - 使用从XML中提取它们的查询进行映射。
两者的混合 - 硬编码属性用于某些项目 - 其setter/getter调用MetaDataItem。
反向混合,如果某些属性需要直接存储(特别是如果您在它们上面排序大型列表):您必须为该元数据硬编码属性使用自己的私有成员,但不要ORM这些属性。硬编码这些值的保存/加载到ORM'd的字符串属性中,如果您还希望能够从MetaDataItem属性更新这些硬编码的元数据项,请在那个位置硬编码它们!
如果您有许多硬编码的元数据属性,除了无限数量之外,您可以使用列表和反射轻松处理XML属性和MetaDataItem属性中的crud。如果所有内容都是硬编码的,则仍然可以使用XML文本属性来加载/保存它们,映射那个属性,而不是其他属性。
使用对象上的LINQ查询对它们进行排序。
我用这个方法取得了巨大成功,并且随着每个项目的编码,事情变得越来越好! 2005 / .Net 1.1因此没有ORM,LINQ,我的第一个VB.net程序等等。但是其他开发人员确实使用SQL服务器的XML查询来读取我的XML。当然,我忘记了这一点,改变了它,并使他们陷入困境:-(
这里是片段。关键是: ORM友好= ORM某些属性,而不是其他属性;允许消费者使用其他属性,但不是某些属性。如果您的ORM不允许这种ala-carte属性选择,您可能可以使用继承或组合来欺骗它。很抱歉我没时间为您的目的发布完整的示例。
好吧,我没有代码示例在这里,在家里。我会编辑并将其明天粘贴。
   Public Property ItemType(ByVal stTagName As String) As String
        Get
            Dim obj As Object
            obj = Me.lstMemberList.Item(stTagName)
            If Not obj Is Nothing Then
                Return CType(obj, foDataItem).Type
            End If
        End Get
        Set(ByVal Value As String)
            Dim obj As Object
            obj = Me.lstMemberList.Item(stTagName)
            If Not obj Is Nothing Then
                CType(obj, foDataItem).Type = Value
            End If
        End Set
    End Property

    Public Function ItemExists(ByVal stTagName As String) As Boolean
        Return Me.lstMemberList.ContainsKey(stTagName)
    End Function

    Public Property ItemValue(ByVal stTagName As String, Optional ByVal Type4NewItem As String = "") As String
        Get
            Dim obj As Object
            obj = Me.lstMemberList.Item(stTagName)
            If obj Is Nothing Then
                Dim stInternalKey As String = ""
                Try
                    stInternalKey = Me.InternalKey.ToString
                Catch
                End Try
                If stTagName <> "InternalKey" Then '' // avoid deadlock if internalkey errs!
                    Throw New ApplicationException("Tag '" & stTagName & _
                      "' does not exist in FO w/ internal key of " & stInternalKey)
                End If
            Else
                Return CType(obj, foDataItem).Value
            End If
        End Get
        Set(ByVal Value As String)
            '' // if child variation form...
            If bLocked4ChildVariation Then
                '' // protect properties not in the list of allowed updatable items 
                If Not Me.GetChildVariationDifferentFields.Contains(stTagName) Then
                    Exit Property
                End If
            End If
            '' // WARNING - DON'T FORGET TO UPDATE THIS LIST OR YOU WILL NEVER FIND THE BUG!
            Select Case stTagName
                Case "PageNum"
                    _PageNum = CInt(Value)
                Case "Left"
                    _Left = CInt(Value)
                Case "Top"
                    _Top = CInt(Value)
                Case "Width"
                    _Width = CInt(Value)
                Case "Height"
                    _Height = CInt(Value)
                Case "Type"
                    _Type = String2Type(Value)
                Case "InternalKey"
                    _InternalKey = CInt(Value)
                Case "UniqueID"
                    _UniqueID = Value
            End Select
            Static MyError As frmErrorMessage
            Dim obj As Object
            If Me.lstMemberList.ContainsKey(stTagName) Then
                Dim foi As foDataItem = CType(Me.lstMemberList.Item(stTagName), foDataItem)
                If foi.Type = "Number" Then
                    Value = CStr(Val(Value))
                End If
                If foi.Value <> Value Then
                    If bMonitorRefreshChanges Then
                        LogObject.LoggIt("Gonna Send Update for Change " & stTagName & " from " & _
                          foi.Value & " to " & Value)
                        If Not Me.FormObjectChanged_Address Is Nothing Then
                            FormObjectChanged_Address(Me, stTagName)
                        End If
                    End If
                End If
                foi.Value = Value
            Else
                Me.lstMemberList.Add(stTagName, New foDataItem(Value, Type4NewItem))
                Me.alOrderAdded.Add(stTagName)
            End If
        End Set
    End Property


  Public Function StringVal(ByVal st As String, Optional ByVal stDefault As String = "") As String
        Try
            StringVal = stDefault
            Return CType(Me.ItemValue(st), String)
        Catch ex As Exception
            Dim bThrowError As Boolean = True
            RaiseEvent ConversionError(ex, "String=" & Me.ItemValue(st), Me, st, bThrowError)
            If bThrowError Then
                LogObject.LoggIt("Error setting tag value in fo.StringVal: " & st)
                Throw New Exception("Rethrown Exception getting value of " & Me.ID & "." & st, ex)
            End If
        End Try
    End Function
    Public Function IntVal(ByVal st As String, Optional ByVal iDefault As Integer = 0) As Integer

    ...

 '' // 'native' values - are normal properties instead of XML properties, which 
    '' // actually makes it harder to deal with b/c of extra updates to sync them, BUT,
    '' // worth it - as they are read much more than written (sorts, wizard builds,
    '' // screen redraws, etc) I can afford to be slow when writing to them, PLUS
    '' // retain the benefits of referencing everything else via ItemValue, PLUS
    '' // these are just the more 'popular' items. 
    Private _Top As Integer
    Private _Left As Integer
    Private _Width As Integer
    Private _Height As Integer
    Private _PageNum As Integer
    Private _Type As pfoType
    Private _InternalKey As Integer
    Private _UniqueID As String

    Public Sub SetNativeValuesFromMyXML()
        _Top = CInt(CType(Me.lstMemberList("Top"), foDataItem).Value)
        _Left = CInt(CType(Me.lstMemberList("Left"), foDataItem).Value)
        _Width = CInt(CType(Me.lstMemberList("Width"), foDataItem).Value)
        _Height = CInt(CType(Me.lstMemberList("Height"), foDataItem).Value)
        _PageNum = CInt(CType(Me.lstMemberList("PageNum"), foDataItem).Value)
        _Type = String2Type(CType(Me.lstMemberList("Type"), foDataItem).Value)
        _InternalKey = CInt(CType(Me.lstMemberList("InternalKey"), foDataItem).Value)
        _UniqueID = CType(Me.lstMemberList("UniqueID"), foDataItem).Value
    End Sub

    Public Property Top() As Integer
        Get
            Return _Top '' // CInt(ItemValue("Top"))
        End Get
        Set(ByVal Value As Integer)
            ItemValue("Top") = Value.ToString
        End Set
    End Property

    Public Property Left() As Integer
        Get
            Return _Left '' //CInt(ItemValue("Left"))
        End Get

    ...

3

过去我曾经通过在对象上添加一个名为Extra的属性来实现这一点,它是字典或类似数据类型。然后您可以自由地填充它并使用LINQ进行查询。


我想要的是IDictionary<string,object>或IDictionaty<string,BaseProperty>。不过问题是关于创建ORM友好的模式的。如果您能再解释一下,我会非常感激。 - Marcin Seredynski

2
你可以添加几个表格,例如:
[EntitiesExtended]
EntitiesExtendedId int
EntitiesExtendedDescription varchar(max)

[Entities_EntitiesExtended]
Entities_EntitiesExtendedId int
EntitiesId int
EntitiesExtendedId int
EntitiesExtendedValue varchar(max)

如果歌曲ID为1,吉他独奏持续34秒,整首歌曲的时长为3分23秒,则可以建模为:

[Entities_EntitiesExtended]
EntitiesId = 1
EntitiesExtendedId = 1
EntitiesExtendedValue = "34"

EntitiesId = 1
EntitiesExtendedId = 2
EntitiesExtendedValue = "203"

[EntitiesExtended]
EntitiesExtendedId = 1
EntitiesExtendedDescription = "GuitarSoloDuration"

[EntitiesExtended]
EntitiesExtendedId = 2
EntitiesExtendedDescription = "Duration"

然后像这样的查询:

select * from Entities e 
join Entities_EntitiesExtended eee on e.id = eee.id 
join EntitiesExtended ee on eee.id = ee.id
where EntitiesExtendedDescription = "GuitarSoloDuration"
and cast(EntitiesExtendedValue as int) > 30 

select * from Entities e 
join Entities_EntitiesExtended eee on e.id = eee.id 
join EntitiesExtended ee on eee.id = ee.id
where EntitiesExtendedDescription = "Duration"
and cast(EntitiesExtendedValue as int) > 180 

1
这是在SQL中完成的方式。我特别寻找ORM友好的方法。强制转换似乎对SQL和O / R映射器都不太友好。感谢您的帮助。 - Marcin Seredynski
@Marcin Seredynski:那么数据应该存储在哪里? - eriksv88
@sv88erik:通过“使用SQL完成”,我是指手写查询,而不考虑任何特定的数据库引擎(如果这是您的意思的话)。数据应存储在关系型数据库中。访问数据不应需要服务器端的强制转换。理想情况下,应该能够将适当的CLR类型存储在适当的SQL列类型中。 - Marcin Seredynski

2
你可以将数据存储在SQL Server中,并使用LINQ to SQL ORM
更新: 你还可以看看NH. LLBL,这是一个ORM/生成器,你的实体将有很多预先生成的代码,并从数据库开始。

自从2010年5月发布的v3版本以来,LLBLGen Pro还提供了模型优先建模功能,可用于更新和创建关系模型。 - Frans Bouma

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接