Как хранить расширяемые метаданные в ORM-формате в .NET? - PullRequest
12 голосов
/ 13 декабря 2010

Кажется, я не могу найти ответы на вопрос "как использовать EAV-подход с инструментами ORM", поэтому я попробую здесь свою удачу.

Предположим, у меня есть Entities Таблица:

ID -> int
Name -> nvarchar(50)

An Images Таблица:

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

И Songs Таблица:

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

Мне нужно добавить расширяемые метаданные в сущности (неизвестнопары ключ-значение с информацией о типе), так что я могу выдавать запросы вроде:

Найдите мне все песни, у которых Duration длиннее 3 минут, с Name, начинающимся с '', С метаданными, удовлетворяющими этим критериям:

  • HasGuitarSolo установлен в true
  • GuitarSoloDuration больше 30 секунд

И сортироватьрезультаты по GuitarSoloDuration в порядке убывания.

Я не хочу создавать в базе данных столбцы HasGuitarSolo, GuitarSoloDuration и т. д. В идеале я хотел бы сохранить их в EAV-подобномсхема или альтернативная схема, которая не требует предварительного знания ключей.

Ответы [ 4 ]

3 голосов
/ 22 декабря 2010

Я делал это в прошлом, помещая свойство Extra в мой объект, который представляет собой словарь или подобный тип данных.Затем вы можете свободно заполнить это данными и выполнить запрос, используя LINQ.

3 голосов
/ 21 декабря 2010

Добавьте столбец в таблицы с именем «метаданные» и вставьте в него XML. Сервер SQL позволяет вам смотреть на блоб, полный XML, как на дополнительные столбцы (с ограничениями).

Для ORM это зависит от того, как структурирован ваш объект.

  • Бесконечно настраиваемые элементы метаданных: вы помещаете пары имя-значение из XML в коллекцию. Если ваш ORM не разрешит этого, поместите его прямо в строковое свойство, установщик может проанализировать его в XML-документе (или более быстром объекте, если вам нужна скорость). Геттер вернет строку. Затем отдельное свойство MetaDataItem (ItemName-> string), которое не является ORM, будет считывать значения из списка метаданных и обновлять / добавлять их с помощью своего установщика.
  • Метадета - это жестко закодированные свойства - сопоставьте их с помощью запроса, извлекающего их из XML.
  • Гибрид двух из них - жестко закодированные свойства для некоторых элементов - имеют свои методы установки / получения, вызывающие MetaDataItem.
  • Обратный гибрид, если определенные свойства должны храниться напрямую (особенно если вы сортируете по ним большие списки): вам нужно жестко закодировать свойства для этих метаданных с их собственными закрытыми членами, но не ORM эти свойства. Жестко запрограммировали сохранение / загрузку этих значений в строковое свойство ORM'd, и, если вы хотите иметь возможность обновлять эти жестко закодированные элементы метаданных из свойства MetaDataItem, также жестко закодируйте их в этом месте!

Если у вас есть целый набор жестко закодированных свойств метаданных, в дополнение к бесконечному количеству, вы можете упростить использование свойства XML и свойства MetaDataItem со списками и отражениями. Если все они жестко закодированы, вы все равно можете использовать текстовое свойство XML для их загрузки / сохранения, сопоставить одно свойство, а не другие.

Сортируйте их с помощью запроса LINQ к объекту.

Я делал это с большим успехом, и с каждой закодированной пулей все работало все лучше и лучше! 2005 / .Net 1.1, поэтому нет ORM, LINQ, моей первой программы VB.net и т. Д. Но другие разработчики использовали SQL-запросы SQL-сервера для чтения моего XML. Конечно, я забыл об этом, изменил и споткнулся: - (

Вот фрагменты. Ключом всего этого является: ORM friendly = ORM одни свойства, а не другие; Разрешите потребителям использовать другие свойства, но не некоторые. Если ваш ORM не позволяет такой выбор свойств по выбору, вы можете использовать наследование или композицию, чтобы обмануть его. Извините, у меня нет времени выложить полный пример для вашей цели.

Ну, у меня нет примера кода здесь, дома. Я буду редактировать и вставлять его завтра.

РЕДАКТИРОВАТЬ как и было обещано, вот фрагмент кода:

   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

    ...
2 голосов
/ 21 декабря 2010

Вы можете сохранить данные на SQL Server и использовать LINQ to SQL ORM .

Обновлен: Вы также можете взглянуть на NH. LLBL , это ORM / Generator, у ваших сущностей будет много предварительно сгенерированного кода, и он запускается из базы данных.

2 голосов
/ 13 декабря 2010

Вы можете добавить пару таблиц, таких как:

[EntitiesExtended]
EntitiesExtendedId int
EntitiesExtendedDescription varchar(max)

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

Так, если песня с идентификатором 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 
...