2010-12-13 12 views
12

Wygląda na to, że nie mogę znaleźć odpowiedzi na pytanie "jak używać podejścia EAV z narzędziami ORM", więc spróbuję tu szczęścia.Jak przechowywać rozszerzalne metadane w sposób przyjazny dla ORM w .NET?

Załóżmy mam Entities Tabela:

ID -> int 
Name -> nvarchar(50) 

Images Tabela:

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

I Songs Tabela:

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

muszę dodać Extensible Metadata podmiotom (nieznane pary klucz-wartość z informacją o typie), więc jestem ble do wydania zapytań typu:

Znajdź mi wszystkie piosenki, które mają Duration dłużej niż 3 minuty, z Name zaczynające się od „W”, z metadanych spełnieniu tych kryteriów:

  • HasGuitarSolo jest ustawiony na true
  • GuitarSoloDuration jest większa niż 30 sekund

i sortowania wyników na GuitarSoloDuration w porządku malejącym.

Nie chcę tworzyć HasGuitarSolo, GuitarSoloDuration itp kolumn w bazie danych, Idealnie chciałbym je przechowywać w schemacie EAV-podobne, lub alternatywnego schematu, który nie wymaga znajomości kluczy z przodu.

Odpowiedz

3

dodawanie kolumny tabel zwanych „metadane” i umieścić w nim XML. Serwer SQL pozwala spojrzeć na obszar blobowy pełen XML, tak jakby był dodatkowymi kolumnami (z ograniczeniami).

Dla ORM zależy to od struktury obiektu.

  • Nieskończenie konfigurowalne elementy metadanych: umieścisz pary nazwa-wartość z XML w kolekcji. Jeśli twoja ORM nie pozwoli na to, włóż to prosto do właściwości string, setter mógłby sparsować ją do dokumentu XML (lub szybszego obiektu, jeśli potrzebujesz prędkości). Getter zwróci ciąg. Następnie oddzielna właściwość "MetaDataItem (ItemName-> string)", która nie jest ORM'd, odczytywałaby wartości z listy metadanych i aktualizowała/dodawała je za pomocą narzędzia ustawiającego.
  • Metadeta to właściwości zakodowane na stałe - zamapuj je za pomocą zapytania, które pobiera je z pliku XML.
  • Hybrydowa z dwóch - zakodowanych właściwości niektórych elementów - ich programy ustawiające/pobierające wywołują MetaDataItem.
  • Odwróć hybrydę, jeśli pewne właściwości muszą być przechowywane bezpośrednio (szczególnie, jeśli sortujesz na nich duże listy): musisz zaimportować właściwości dla tych metadanych z własnymi członkami prywatnymi, ale nie ORM tych właściwości. Na stałe zapisano/wczytałem te wartości do właściwości łańcuchowej, która jest ORM'd, i jeśli chcesz mieć możliwość aktualizowania tych zakodowanych metadanych z właściwości MetaDataItem, zakoduj je również w tym miejscu!

Jeśli masz cały szereg zakodowanych właściwości metadanych, oprócz nieskończonej ilości, możesz łatwo crud w własności XML i MetaDataItem z list i refleksji. Jeśli wszystkie są zakodowane na stałe, nadal możesz użyć właściwości tekstowej XML, aby załadować/zapisać je, zmapować tę właściwość, a nie inne.

Posortuj je za pomocą kwerendy LINQ na obiekcie.

Zrobiłem to z wielkim sukcesem i przy każdym kodowaniu kulkowym, wszystko działało coraz lepiej! 2005/.Net 1.1 więc nie ORM, LINQ, mój pierwszy program VB.net itp. Ale inni programiści użyli zapytań XML serwera SQL do odczytu mojego XML. Oczywiście o tym zapomniałem, zmieniłem i potknąłem :-(

Oto fragmenty. Kluczem do tego wszystkiego jest: Przyjazny ORM = ORM niektóre właściwości, nie inne; Pozwól konsumentom używać innych właściwości, ale Nie. Jeśli ORM nie zezwala na wybór właściwości ala-carte, możesz użyć dziedziczenia lub kompozycji, aby oszukać. Przykro mi, nie mam czasu, aby opublikować pełny przykład dla Twojego celu:

No cóż, nie mam tu próbki kodu, w domu, będę ją edytować i wkleić jutro.nie.

EDIT zgodnie z obietnicą, oto Fragment kodu:

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

Można dodać kilka tabel, takich jak:

[EntitiesExtended] 
EntitiesExtendedId int 
EntitiesExtendedDescription varchar(max) 

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

Więc jeśli piosenka id 1 miał solówkę gitarową 34 sekund i trwał przez 3 minuty i 23 sekund, to może być modelowane jako:

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

EntitiesId = 1 
EntitiesExtendedId = 2 
EntitiesExtendedValue = "203" 

[EntitiesExtended] 
EntitiesExtendedId = 1 
EntitiesExtendedDescription = "GuitarSoloDuration" 

[EntitiesExtended] 
EntitiesExtendedId = 2 
EntitiesExtendedDescription = "Duration" 

a następnie pytania takie jak:

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

Tak można to zrobić w SQL. Szukam szczególnie przyjaznego dla ORM sposobu. Przesyłanie nie wydaje się zbyt przyjazne ani dla SQL, ani dla maperów O/R. Dzięki za pomoc. –

+0

@Marcin Seredynski: Gdzie przechowywać dane? – sv88erik

+0

@ sv88erik: przez "done in SQL", miałem pisanie zapytań ręcznie, nie mając na myśli żadnego konkretnego silnika bazy danych (jeśli o to ci chodziło). Dane powinny być przechowywane w relacyjnej bazie danych. Uzyskiwanie dostępu do danych nie powinno wymagać przesyłania po stronie serwera. W idealnej sytuacji powinno być możliwe przechowywanie odpowiednich typów CLR w odpowiednich typach kolumn SQL. –

2

można przechowywać dane w SQL Server i za pomocą LINQ to SQL ORM.

Aktualizacja: Można również spojrzeć na NH. LLBL, to ORM/Generator, twoje jednostki będą mieć dużo kodu wstępnie generowane, a to zaczyna się od bazy danych.

+0

Od wersji v3 (wydanej w maju 2010 r.) LLBLGen Pro oferuje także pierwsze funkcje modelowania, które można wykorzystać do aktualizacji i tworzenia modeli relacyjnych. –

3

Zrobiłem to w przeszłości, umieszczając na moim obiekcie właściwość Extra, która jest Słownikiem lub podobnym typem danych. Następnie można dowolnie wypełnić to danymi i zapytać przy użyciu LINQ.

+0

IDictionary or IDictionaty jest tym, do czego dążę. Pytanie dotyczyło jednak stworzenia schematu przyjaznego dla ORM. Byłbym bardzo wdzięczny, gdybyś mógł wyjaśnić nieco więcej? –