2010-12-13 47 views
12

Il semble que je suis incapable de trouver des réponses à la question "comment utiliser l'approche EAV avec les outils ORM", alors je vais tenter ma chance ici.Comment stocker des métadonnées extensibles de manière ORM dans .NET?

Supposons que j'ai un Entities Tableau:

ID -> int 
Name -> nvarchar(50) 

Un Images Tableau:

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

Et Songs Tableau:

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

Je dois ajouter des métadonnées extensibles aux entités (paires clé-valeur inconnues avec info de type), de sorte que je suis un ble d'émettre des requêtes telles que:

Me trouver tous les morceaux qui ont un Duration plus de 3 minutes, avec un Name commençant par « La », avec des métadonnées conformes à ces critères:

  • HasGuitarSolo est à true
  • GuitarSoloDuration est supérieur à 30 secondes

et trier les résultats sur GuitarSoloDuration par ordre décroissant.

Je ne veux pas créer HasGuitarSolo, GuitarSoloDuration, etc. colonnes dans la base de données, idéalement, je voudrais les stocker dans un schéma semblable à EAV, ou un autre schéma qui ne nécessite pas une connaissance des clés à l'avant.

Répondre

3

Ajouter une colonne aux tables appelées « métadonnées » et de mettre XML en elle. Le serveur SQL vous permet de regarder un blob plein de XML comme s'il s'agissait de colonnes supplémentaires (avec des limitations).

Pour ORM, cela dépend de la structure de votre objet. Infiniment Éléments de métadonnées personnalisables: vous mettez les paires nom-valeur du code XML dans une collection. Si votre ORM ne le permet pas, mettez le directement dans une propriété de chaîne, le setter peut l'analyser dans un document XML (ou un objet plus rapide si vous avez besoin de vitesse). Getter retournerait la chaîne. Ensuite, une propriété séparée 'MetaDataItem (ItemName-> string)' qui n'est pas ORM'd lirait les valeurs de la liste de métadonnées et les mettrait à jour/les ajouterait avec son setter.

  • Les métadonnées sont des propriétés codées en dur - mappez-les à l'aide d'une requête qui les extrait du fichier XML.
  • Hybride des deux - propriétés codées en dur pour certains éléments - ont leurs setters/getters appellent MetaDataItem.
  • Inverse hybride si certaines propriétés doivent être directement stockées (surtout si vous triez de grandes listes): vous devez coder les propriétés de ces métadonnées avec leurs propres membres privés, mais ne les ORM pas. Hardcoded l'enregistrement/chargement de ces valeurs dans la propriété de chaîne qui est ORM'd, et si vous voulez être capable de mettre à jour ces éléments de métadonnées codés en dur de la propriété MetaDataItem aussi, hardcode eux aussi cet endroit! Si vous avez tout un tas de propriétés de métadonnées codées en dur, en plus de la quantité infinie, vous pouvez faciliter le crud dans la propriété XML et la propriété MetaDataItem avec des listes et des réflexions. Si tous sont codés en dur, vous pouvez toujours utiliser la propriété de texte XML pour les charger/enregistrer, mapper cette propriété, pas les autres.

    Triez-les avec une requête LINQ sur l'objet. Je l'ai fait avec beaucoup de succès et avec chaque puce codée, les choses fonctionnaient de mieux en mieux! 2005/.Net 1.1 donc pas d'ORM, LINQ, mon premier programme VB.net etc. Mais d'autres développeurs ont utilisé l'interrogation XML du serveur SQL pour lire mon XML. Bien sûr, j'ai oublié ça, je l'ai changé, et je les ai fait trier :-(

    Voici des extraits de code: ORM friendly = ORM quelques propriétés, pas d'autres; Permettre aux consommateurs d'utiliser d'autres propriétés, mais Si votre ORM n'autorise pas une telle sélection de propriétés à la carte, vous pourriez utiliser l'héritage ou la composition pour tromper le fichier Désolé, je n'ai pas le temps d'en donner un exemple complet. Eh bien, je n'ai pas l'échantillon de code ici, à la maison, je vais l'éditer et le coller dès demain

    EDIT comme promis, voici la extrait de code:

    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

    Vous pouvez ajouter quelques tables comme:

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

    Donc, si id chanson 1 avait un solo de guitare de 34 secondes et a duré 3 minutes et 23 secondes, il pourrait être modelée comme:

    [Entities_EntitiesExtended] 
    EntitiesId = 1 
    EntitiesExtendedId = 1 
    EntitiesExtendedValue = "34" 
    
    EntitiesId = 1 
    EntitiesExtendedId = 2 
    EntitiesExtendedValue = "203" 
    
    [EntitiesExtended] 
    EntitiesExtendedId = 1 
    EntitiesExtendedDescription = "GuitarSoloDuration" 
    
    [EntitiesExtended] 
    EntitiesExtendedId = 2 
    EntitiesExtendedDescription = "Duration" 
    

    et puis des requêtes telles que:

    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

    Voici comment cela pourrait être fait en SQL. Je cherche spécifiquement une manière ORM-amicale. Casting ne semble pas très sympathique ni pour SQL ni pour les mappeurs O/R. Merci d'avoir essayé d'aider. –

    +0

    @Marcin Seredynski: Où stocker les données alors? – sv88erik

    +0

    @ sv88erik: par "fait en SQL", j'avais écrit des requêtes à la main, n'ayant pas de moteur de base de données spécifique en tête (si c'est ce que vous vouliez dire). Les données doivent être stockées dans une base de données relationnelle. L'accès aux données ne devrait pas nécessiter de casting côté serveur. Idéalement, il devrait être possible de stocker les types CLR appropriés dans les types de colonne SQL appropriés. –

    2

    Vous pouvez stocker les données dans SQL Server et en utilisant LINQ to SQL ORM.

    Mise à jour: Vous pouvez également jeter un oeil à NH. LLBL, c'est un ORM/générateur, vos entités auront beaucoup de code pré-généré, et il commence à partir de la base de données.

    +0

    Depuis la version 3 (publiée en mai 2010), LLBLGen Pro propose également des premières fonctionnalités de modélisation qui peuvent être utilisées pour mettre à jour et créer des modèles relationnels. –

    3

    Je l'ai fait par le passé en mettant une propriété Extra sur mon objet, qui est un dictionnaire ou un type de données similaire. Vous pouvez ensuite remplir ce champ avec des données et interroger en utilisant LINQ.

    +0

    IDictionary ou IDictionaty est ce que je vise. La question portait sur la création d'un schéma compatible ORM, cependant. J'apprécierais vraiment si vous pourriez expliquer un peu plus? –