2010-12-13 11 views
12

Parece que no puedo encontrar ninguna respuesta al "cómo utilizar el enfoque de EAV con las herramientas de ORM", así que probaré mi suerte aquí.¿Cómo almacenar metadatos extensibles de manera ORM en .NET?

Supongamos que tengo una Entities Tabla:

ID -> int 
Name -> nvarchar(50) 

Un Images Tabla:

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

Y un Songs Tabla:

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

tengo que añadir metadatos extensible a las entidades (pares clave-valor desconocidos con tipo de información), para que yo sea un bles para la emisión de consultas como:

Encuéntrame todas las canciones que tienen un Duration más de 3 minutos, con un Name que empiezan con 'El', con metadatos que cumpla estos criterios:

  • HasGuitarSolo se establece en true
  • GuitarSoloDuration es mayor que 30 segundos

y ordenar los resultados en GuitarSoloDuration en orden descendente.

no quiero crear HasGuitarSolo, GuitarSoloDuration, etc. columnas en la base de datos, Tu lugar ideal para que los guarde en un esquema de EAV-similares, o un esquema alternativo que no requiere un conocimiento de las claves en la delantera.

Respuesta

3

Añadir una columna a las tablas llamadas 'metadatos' y poner en ella XML. El servidor SQL le permite ver un blob lleno de XML como si se tratara de columnas adicionales (con limitaciones).

Para ORM, depende de cómo está estructurado su objeto.

  • Elementos de metadatos infinitamente personalizables: coloca los pares nombre-valor del XML en una colección. Si su ORM no permite esto, póngalo directamente en una propiedad de cadena, el colocador podría analizarlo en un documento XML (u objeto más rápido si necesita velocidad). Getter devolvería la cadena. Entonces una propiedad separada 'MetaDataItem (ItemName-> string)' que no está ORM'd leería los valores de la lista de metadatos y los actualizaría/agregaría con su setter.
  • Metadeta son propiedades codificadas: asignelas mediante una consulta que las extraiga del XML.
  • Híbrido de las dos propiedades de hardcoded para algunos elementos: tienen sus setters/getters llamados MetaDataItem.
  • Invertir híbrido si ciertas propiedades necesitan almacenarse directamente (especialmente si está ordenando listas grandes en ellas): tiene que codificar las propiedades de esos metadatos con sus propios miembros privados, pero no ORM esas propiedades. Hardcoded el guardado/carga de esos valores en la propiedad de cadena que está ORM'd, y si usted también desea poder actualizar esos elementos de metadatos codificados de la propiedad MetaDataItem, cdíquelos también en ese lugar.

Si tiene un conjunto completo de propiedades de metadatos codificados, además de la cantidad infinita, puede simplificar la crud en la propiedad XML y la propiedad MetaDataItem con listas y reflejos. Si todos están codificados, aún puede usar la propiedad de texto XML para cargar/guardarlos, mapear esa propiedad, no las otras.

Ordenarlos con una consulta LINQ en el objeto.

Hice esto con gran éxito y con cada código de bala, las cosas funcionó mejor y mejor! 2005/.Net 1.1 así que no ORM, LINQ, mi primer programa de VB.net, etc. Pero otros desarrolladores usaron las consultas XML de SQL Server para leer mi XML. Por supuesto, me olvidé de esto, lo cambié y lo estropeé :-(

Aquí hay fragmentos. La clave de todo esto es: ORM friendly = ORM algunas propiedades, otras no; Permitir a los consumidores usar otras propiedades, pero no algunos Si su ORM no permite esa selección de propiedad a la carta, es posible que pueda usar herencia o composición para engañarlo. Lo siento, no tengo tiempo para publicar un ejemplo completo para su propósito.

Bueno, no tengo el ejemplo de código aquí, en casa. Lo editaré y lo pegaré en la mañana.

EDIT según lo prometido, aquí está el fragmento de código:

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

Se podría añadir un par de mesas como:

[EntitiesExtended] 
EntitiesExtendedId int 
EntitiesExtendedDescription varchar(max) 

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

Así que si la canción Identificación del 1 tenía un solo de guitarra de 34 segundos y se prolongó durante 3 minutos y 23 segundos que podría ser modelado como:

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

EntitiesId = 1 
EntitiesExtendedId = 2 
EntitiesExtendedValue = "203" 

[EntitiesExtended] 
EntitiesExtendedId = 1 
EntitiesExtendedDescription = "GuitarSoloDuration" 

[EntitiesExtended] 
EntitiesExtendedId = 2 
EntitiesExtendedDescription = "Duration" 

y luego consultas como:

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

Así es como se podría hacer en SQL. Estoy buscando específicamente una forma amigable con ORM. Casting no parece muy amigable ni con SQL ni con los mapeadores O/R. Gracias por intentar ayudar sin embargo. –

+0

@Marcin Seredynski: ¿Dónde almacenar los datos, entonces? – sv88erik

+0

@ sv88erik: por "hecho en SQL", tenía consultas escritas a mano, sin tener en mente ningún motor de base de datos específico (si eso es lo que quería decir). Los datos deben almacenarse en una base de datos relacional. El acceso a los datos no debería requerir el envío por el lado del servidor. Idealmente, debería ser posible almacenar tipos de CLR apropiados en los tipos de columna SQL apropiados. –

2

puede almacenar los datos en SQL Server y su uso LINQ to SQL ORM.

Actualizado: También puede echar un vistazo a NH. LLBL, este es un ORM/generador, sus entidades tendrán una gran cantidad de código generado previamente, y se parte de la base de datos.

+0

Desde v3 (lanzado en mayo de 2010), LLBLGen Pro también ofrece las primeras características de modelado del modelo que se pueden utilizar para actualizar y crear modelos relacionales. –

3

He hecho esto en el pasado al poner una propiedad Extra en mi objeto, que es un diccionario o tipo de datos similar. A continuación, puede poblarlo libremente con datos y realizar consultas utilizando LINQ.

+0

IDictionary o IDictionaty es lo que pretendo. Sin embargo, la cuestión era crear un esquema compatible con ORM. Realmente te agradecería si pudieras explicar un poco más? –

Cuestiones relacionadas