I am developing a client-side app which passes data back to a database. The client has the ability to create an object of type PlaylistItem. I do not wait for the database to respond with a generated ID when creating a PlaylistItem. Instead, I let the client generate the ID, but write the PlaylistItem to the database with a PK of { PlaylistID, PlaylistItemID }. PlaylistID is generated by the server. I followed this approach after talking things over a bit with Jon Skeet
Now, I'm trying to get things jiving in NHibernate, but I'm running into some pretty hefty issues. All the resources I read keep stating, "NHibernate heavily dissuades against the use of composite keys. Only use them if you're working on a legacy DB." I'm not working on a legacy DB, so I assume I should make the change. However, I have no idea what my alternatives would be in such a scenario.
Here's PlaylistItem's NHibernate mapping and corresponding class:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Streamus" namespace="Streamus.Backend.Domain">
<class name="PlaylistItem" table="[PlaylistItems]" lazy="false" >
<composite-id>
<key-property name="Id" />
<key-property name="PlaylistId"/>
</composite-id>
<property name="Title" not-null="true" />
<many-to-one name="Playlist" column="PlaylistId"/>
</class>
</hibernate-mapping>
[DataContract]
public class PlaylistItem
{
[DataMember(Name = "playlistId")]
public Guid PlaylistId
{
get { return Playlist.Id; }
set { Playlist.Id = value; }
}
public Playlist Playlist { get; set; }
[DataMember(Name = "id")]
public Guid Id { get; set; }
// Store Title on PlaylistItem as well as on Video because user might want to rename PlaylistItem.
[DataMember(Name = "title")]
public string Title { get; set; }
public PlaylistItem()
{
// Id shall be generated by the client. This is OK because it is composite key with
// PlaylistId which is generated by the server.
Id = Guid.Empty;
Title = string.Empty;
}
private int? _oldHashCode;
public override int GetHashCode()
{
// Once we have a hash code we'll never change it
if (_oldHashCode.HasValue)
return _oldHashCode.Value;
bool thisIsTransient = Equals(Id, Guid.Empty);
// When this instance is transient, we use the base GetHashCode()
// and remember it, so an instance can NEVER change its hash code.
if (thisIsTransient)
{
_oldHashCode = base.GetHashCode();
return _oldHashCode.Value;
}
return Id.GetHashCode();
}
public override bool Equals(object obj)
{
PlaylistItem other = obj as PlaylistItem;
if (other == null)
return false;
// handle the case of comparing two NEW objects
bool otherIsTransient = Equals(other.Id, Guid.Empty);
bool thisIsTransient = Equals(Id, Guid.Empty);
if (otherIsTransient && thisIsTransient)
return ReferenceEquals(other, this);
return other.Id.Equals(Id);
}
}
NHibernate throws the exception with error message "Invalid index n for this SqlParameterCollection with Count=n." which I understand to arise when there is a duplicate declaration in an hbm.xml file. From my understanding, this would arise because I define PlaylistId as a key-property and again in the many-to-one relationship.
What are my options here? I'm pretty stumped.
You could use a key-many-to-one
instead of key-property
, i.e.
<class name="PlaylistItem" table="[PlaylistItems]" lazy="false" >
<composite-id>
<key-property name="Id" />
<key-many-to-one name="Playlist" column="PlaylistId"/>
</composite-id>
<property name="Title" not-null="true" />
</class>
Then your class would look like...
[DataContract]
public class PlaylistItem
{
// Your composite key...
[DataMember(Name = "id")]
public Guid Id { get; set; }
public Playlist Playlist { get; set; }
// Store Title on PlaylistItem as well as on Video because user might want to rename PlaylistItem.
[DataMember(Name = "title")]
public string Title { get; set; }
// rest of class...
}