views:

56

answers:

3

Hi!

I've been wrecking my mind on how to get my tagging of entities to work. I'll get right into some database structuring:

tblTag  
TagId - int32 - PK  
Name

tblTagEntity  
TagId - PK  
EntityId - PK  
EntityType - string - PK

tblImage  
ImageId - int32 - PK  

tblBlog  
BlogId - int32 - PK  

class Image  
Id  
EntityType { get { return "MyNamespace.Entities.Image"; }  
IList<Tag> Tags;

class Blog  
Id  
EntityType { get { return "MyNamespace.Entities.Blog"; }  
IList<Tag> Tags;

The obvious problem I have here is that EntityType is an identifer but doesn't exist in the database. If anyone could help with the this mapping I'd be very grateful.

A: 

You could make EntityType an Enum in your code. And/or, you could try making EntityType an actual entity in your database (tblEntityType).

Jesse
A: 

You don't need the entity type. Take a look at any-type mapping (it stores the type name in the database in the relation table, but you don't need it in the entity model).

See this blog post by ayende.


Edit: tried to write an example.

You could have an own table for each tagged object, this is easy and straight forward, you don't even need any types:

<class name="Tag">
  <!-- ... -->
  <property name="Name"/>
</class>

<class name="Image">
  <!-- ... -->
  <bag name="Tags" table="Image_Tags">
    <key column="Image_FK"/>
    <many-to-many class="Tag" column="TagId "/>
  </bag>
</class>

Tried to use some advanced features to map it into a single table, but I think it doesn't work this way:

<class name="Tag">
  <!-- ... -->
  <property name="Name"/>
  <bag name="Objects" table="tblTagEntity" access="noop">
    <key column="TagId"/>
    <many-to-any id-type="System.Int64" meta-type="System.String">

      <meta-value 
        value="IMAGE"
        class="Image"/>
      <meta-value 
        value="BLOG"
        class="Blog"/>

      <column name="EntityType"/>
      <column name="EntityId"/>
    </many-to-any>
  </bag>
</class>

<class name="Image">
  <!-- ... -->
  <bag name="Tags" table="tblTagEntity" where="EntityType='IMAGE'">
    <key column="EntityId"/>
    <many-to-many class="Tag" column="TagId "/>
  </bag>
</class>

The tricks here are:

  • access="noop" to specify the foreign key without having a property in the entity model, see this post.
  • where="EntityType='IMAGE'" to filter the loaded data.

The problem is that most probably the EntityType is not set to any useful value. This could be fixed somewhere, but I don't think that it is worth the effort.

Someone else has probably a better idea.


Edit 2: another (working) solution

make the association table an entity:

in short:

  • Tag => TagEntity: not mapped or one-to-many inverse (noop)
  • TagEntity => Tag: many-to-one
  • TagEntity => Object: any
  • Object => TagEntity: one-to-many inverse

This should work straight forward.

classes:

class Tag
{
  string Name { get; set; }
}

class TagEntity
{
  Tag Tag { get; set; }
  object Entity { get; set; }
}

class Image
{
  IList<TagEntity> tags { get; private set; }
}

The only drawback seems to be that you have to make sure that the bidirectional associations are consistent without loading to much data. Note that inverse collections are not stored.


Edit 2: Performance notes

When you add / remove tags, you could do a trick. TagEntity has a reference to the tagged entity. The Entity also has a list of TagEntities, but this is marked as inverse. (This means, they are loaded, but not stored.)

You can add and remove tags without loading the Entity an without loading all the tags.

Adding:

  • Get Tag to add (or load proxy if you have the id of the tag)
  • Load Entity (just proxy, using session.Load, no db access here)
  • create new TagEntity, assign tag and entity-proxy
  • save TagEntity

Removing:

  • Get TagEntity to remove
  • delete TagEntity.

Within the session, you don't have this tag assigned to/removed from the TagEntity. This works fine assumed that you only add or remove tags within this transaction.

I you define a list of TagEntities on the Tag, you can do the same, without loading all the TagEntities just to add or remove one.

Stefan Steinegger
Ok, but do I need TagEntity as an entity? And how would the mapping for that look in that case? I've already looked at any and many-to-any without getting any smarter =/
ZNS
No, you don't need TagEntity if there aren't any more properties in it. Just map it as `<many-to-any>`.
Stefan Steinegger
Thanks. However I've read that blogpost 20 times I think but I still can't implement that in my scenario. The problem is that my scenario is the other way around. I need to add one type of entity to several others.
ZNS
Thank you for the example! It's the closest I've been to a solution so far at least. It inserts into tblImage and tblTag but it never tries to insert a value for EntityType into tblTagEntity.
ZNS
Yes, that's the problem with "solution 2". Because the only place where EntityType is mapped, it is "noop" and therefore not in the entity. The other side does not know EntityType. It would need some hack to fix this (eg. custom SQL for insert/update), but I wouldn't suggest such things as long as there are easier solutions. See "solution 3".
Stefan Steinegger
Nice idea but now it dosn't run any insert on tblTag at all. I'll post my mappings.
ZNS
Edited my original post with current mappings for your third solution. Not sure about best practices here.
ZNS
Got it to work. Posted my final mappings below. Thank you very much for all your help!
ZNS
A: 

Got Stefans final solution to work! Here's my final mappings:

Image

<bag name="TagEntites" table="tblTagEntity" cascade="all" fetch="join" inverse="true" where="EntityType='EntityImage'">
<key column="EntityId"></key>
<one-to-many class="TagEntity" />
</bag>

TagEntity

<id name="Id">
  <column name="TagEntityId"></column>
  <generator class="identity" />
</id>
<any name="Entity" id-type="System.Int32" meta-type="System.String">
  <meta-value value="EntityImage" class="Image" />
  <column name="EntityType"></column>
  <column name="EntityId"></column>
</any>
<many-to-one name="Tag" class="Tag" cascade="all" fetch="join">
  <column name="TagId"></column>
</many-to-one>
ZNS