splatto
splatto

Reputation: 3217

NHibernate casting issue (suspected cause is incorrect mapping)

I'm having trouble mapping my M:1 relationship between Petition and PetitionSignature in NHibernate. This is a type of relationship I've mapped before, but I just can't get it working right now.

The method I am trying to call in my provider is designed to return a collection of PetitionSignatures based on the Petition object that was passed in:

public IList<PetitionSignature> GetByPetition(Petition p)
{
    IList<PetitionSignature> Signatures = (IList<PetitionSignature>)_session.CreateCriteria(typeof(PetitionSignature))
        .Add(Restrictions.Eq("Petition", p))
        .AddOrder(Order.Desc("Id")).List();
    return Signatures.OrderBy(sig => sig.Date).ToList();
}

My Petition class is defined as:

public class Petition : IPetition
{
    public virtual int Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string Text { get; set; }
    public virtual string Slug { get; set; }
    public virtual DateTime Date { get; set; }
    public virtual IList<PetitionSignature> Signatures { get; set; }
}

My PetitionSignature class is defined as:

public class PetitionSignature : IPetitionSignature
{
    public virtual int Id { get; set; }
    public virtual Petition Petition { get; set; }
    public virtual string Name { get; set; }
    public virtual string EmailAddress { get; set; }
    public virtual string PhoneNumber { get; set; }
    public virtual string StreetAddress { get; set; }
    public virtual DateTime Date { get; set; }
    public virtual bool OkToEmail { get; set; }

}

My Petition mapping document is:

<id name="Id" column="Id" type="int">
  <generator class="native" />
</id>

<property name="Title" column="Title"></property>
<property name="Text" column="Text"></property>
<property name="Date" column="Date"></property>
<property name="Slug" column="Slug"></property>

<bag name="Signatures" lazy="true" cascade="all-delete-orphan" inverse="true">
  <key column="Id"/>
  <one-to-many class="PetitionSignature"/>
</bag>

And my PetitionSignature mapping document is as follows:

<property name="Name" column="Name"></property>
<property name="EmailAddress" column="EmailAddress"></property>
<property name="Date" column="Date"></property>
<property name="OkToEmail" column="OkToEmail"></property>
<property name="PhoneNumber" column="PhoneNumber"></property>
<property name="StreetAddress" column="StreetAddress"></property>

  <many-to-one name="Petition" class="Petition" column="Petition" cascade="all" />

These are the columns of my Petition table: Petition Table

And these are the columns of my PetitionSignature table: PetitionSignature Table

So here's what happens. I run my integration test in debug mode and test this out. I hydrate my Petition object with no trouble, and pass it to this method in the PetitionSignatureProvider class. That's when this happens: Exception Time!

Of course, this conversion can be made. Creating another method in the provider that accepts a PetitionId, the conversion is made fine. This leads me to believe the problem is with my mapping, shown above.

Any ideas?

Upvotes: 1

Views: 296

Answers (3)

Diego Mijelshon
Diego Mijelshon

Reputation: 52725

Use .List<PetitionSignature>() instead of .List() with Criteria. That will return the generic collection that you're expecting, without the need for casts.

Now, there is a mapping error which is preventing you from doing this in a much simpler way:

<bag name="Signatures" cascade="all-delete-orphan" inverse="true">
  <key column="Petition"/> <!-- This is the FK -->
  <one-to-many class="PetitionSignature"/>
</bag>

With that, you can just use petition.Signatures, no need for a Criteria query.

Upvotes: 1

Elian Ebbing
Elian Ebbing

Reputation: 19027

The result of a criteria query is not a generic list, it returns an object that implements IList, so you can't just cast it to a generic list. You can however convert it to a generic list, by using the extension methods on IEnumerable. The following code should work:

public IList<PetitionSignature> GetByPetition(Petition p)
{
    return _session.CreateCriteria(typeof(PetitionSignature))
        .Add(Restrictions.Eq("Petition", p))
        .AddOrder(Order.Desc("Id"))
        .List()
        .Cast<PetitionSignature>()
        .OrderBy(sig => sig.Date)
        .ToList();
}

But you already have a Petition object. If this object is fetched via NHibernate by the same session, then you can just write:

public IList<PetitionSignature> GetByPetition(Petition p)
{
    return p.Signatures.OrderBy(sig => sig.Date).ToList();
}

Or even better, you don't need a method at all. Just write

p.Signatures.OrderBy(sig => sig.Date)

whenever you need the signatures of a specific petition ordered by date. Besides this, I have a couple of suggestions regarding your code:

  • There is a mistake in you mapping file. The <key> element in your bag should refer to the foreign key field of the PetitionSignature table. It should be: <key column="Petition" />

  • If your NHibernate version is not too old, I would use the new QueryOver api, or NHibernate.Linq. These are both typesafe query APIs that use lambda expression instead of magic strings.

  • I think you want to use a <set> instead of a <bag>. See this stackoverflow question for more information about sets and bags.

Upvotes: 4

millimoose
millimoose

Reputation: 39950

System.Collections.ArrayList does not implement the generic interface System.Collections.Generic.IList<T>, but the non-generic interface System.Collections.IList. These shouldn't be compatible unless C# does some magic I'm not aware of in this case.

If NHibernate has an API that returns generic collections, you should use that to retrieve results. Otherwise, you could use IEnumerable.OfType() or IEnumerable.Cast().

If this works in a different method, it could be that NHibernate returns a generic collection when navigating an association that uses a generic type in the entity class, but returns a non-generic collection when using the ICriteria API to query for a list of entities. The documentation also implies that the newer QueryOver API also uses generics.

Upvotes: 1

Related Questions