It seems I am unable to find any answers to "how to use EAV approach with ORM tools" question, so I'll try my luck here.
Suppose I have an Entities
Table:
ID -> int
Name -> nvarchar(50)
An Images
Table:
EntityID -> int
Width -> int
Height -> int
And a Songs
Table:
EntityID -> int
Duration -> decimal(12,3)
I need to add extensible metadata to the entities (unknown key-value pairs with type info), so that I'm able to issue queries like:
Find me all the Songs that have a Duration
longer than 3 minutes, with a Name
starting with 'The', with metadata fulfilling these criteria:
HasGuitarSolo
is set to trueGuitarSoloDuration
is greater than 30 secondsAnd sort the results on GuitarSoloDuration
in descending order.
I don't want to create HasGuitarSolo
, GuitarSoloDuration
, etc. columns in the database, Ideally I would like to store them in an EAV-like schema, or an alternative schema that doesn't require a knowledge of the keys up front.
Add a column to the tables called 'metadata' and put XML in it. SQL server allows you to look at a blob full of XML as if it were additional columns (with limitations).
For ORM, it depends on how your object is structured.
If you have a whole bunch of hardcoded metadata properties, in addition to the infinite amount, you can easy the crud in the XML property and MetaDataItem property with lists and reflection. If all are hardcoded you can still use the XML text property to load/save them, map that one property, not the others.
Sort them with a LINQ query on the object.
I did this with great success and with each bullet coded, stuff worked better and better! 2005/.Net 1.1 so no ORM, LINQ, my first VB.net program etc. But other developers did use SQL server's XML querying to read my XML. Of course I forgot about this, changed it, and tripped them up :-(
Here are snippets. Key of this all is: ORM friendly = ORM some properties, not others; Allow consumers to use other properties, but not some. If your ORM doesn't allow such ala-carte property selection, you might be able to use inheritance or composition to trick it. Sorry I don't have time to post full example for your purpose.
Well I don't have the code sample here, at home. I will edit and paste it in tommorrow.
EDIT as promised, here's the code snippet:
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
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With