Frage

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.

War es hilfreich?

Lösung

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...
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top