views:

261

answers:

2

I have created 3 tables in my database and put data into them. The 3 tables all have foreign keys joining them together. Below are the table classes and there mappings. When I run the query listed at the end I get IList<> of the objects and they have the data from all 3 tables. However, my HQL query is only from the top most table. How can I get back just the results from the top most table?

These are my classes:

public class Technology
{
    public virtual int Id { get; private set; }
    public virtual string Name { get; set; }
    public virtual int SortOrder { get; set; }
    public virtual string Abbreviation { get; set; }
    public virtual IList<TechnologyDescription> TechnologyDescriptions { get; private set; }

    public Technology()
    {
        TechnologyDescriptions = new List<TechnologyDescription>();
    }

    public virtual void AddTechnologyDescription(TechnologyDescription technologyDescription)
    {
        technologyDescription.Technology = this;
        TechnologyDescriptions.Add(technologyDescription);
    }
}

public class TechnologyDescription
{
    public virtual int Id { get; private set; }
    public virtual Technology Technology { get; set; }
    public virtual string Description { get; set; }
    public virtual DescriptionType DescriptionType { get; set; }
}

public class DescriptionType
{
    public virtual int Id {get; private set;}
    public virtual string Type { get; set; }
}

These are my mapping objects:

public class TechnologyMap : ClassMap<Technology>
{
    public TechnologyMap()
    {
        Id(x => x.Id);
        Map(x => x.Name);
        Map(x => x.SortOrder);
        Map(x => x.Abbreviation);
        HasMany(x => x.TechnologyDescriptions)
                .Inverse()
                .Cascade.All();
    }
}

public class TechnologyDescriptionMap  : ClassMap<TechnologyDescription>
{
    public TechnologyDescriptionMap()
    {
        Id(x => x.Id);
        References(x => x.Technology);
        Map(x => x.Description);
        References(x => x.DescriptionType);
    }
}

public class DescriptionTypeMap : ClassMap<DescriptionType>
{
    public DescriptionTypeMap()
    {
        Id(x => x.Id);
        Map(x => x.Type);
    }
}

And this is my HQL code:

IQuery q = session.CreateQuery("from Technology T");
IList technologies = q.List();
+1  A: 

I don't know if it is possible using HQL, but using NHibernate's Criteria API, you can do this:

ICriteria criteria = session.CreateCriteria (typeof(Technology));

criteria.SetFetchMode ("TechnologyDescriptions", FetchMode.Lazy);

var list = criteria.List<Technology>();

However, this is probably not really what you want. The TechnologyDescriptions won't be fetched right now, but they will be fetched once you access them (that is: the first time you call the TechnologyDescriptions property).

When working with NHibernate, you shouldn't think in terms of 'data'. Rather, you should think in terms of 'entities'.
When retrieving an entity, you want to retrieve the entity entirly (directly, or in a lazy fashion). It is not possible to retrieve an entity partially, and this is quite obvious; What should NHibernate do with an entity that you've retrieved partially, when you try to save that entity ?

Something else that pops in my mind: I suppose you want to retrieve the Technologies, and nothing related because you want to display them in an overview or something like that ? In such case, you should take a look at 'Transformations'. You could for instance create an additional class which is called TechnologyView, which looks like this:

public class TechnologyView
{
    public int Id
    {
        get;
        private set;
    }

    public string Name
    {
        get;
        private set;
    }

    public string Abbreviation
    {
        get;
        private set;
    }

    private TechnologyView()
    {
       // Private constructor, required for NH
    }

    public TechnologyView( int id, string name, string abbreviation )
    {
       this.Id = id;
       this.Name = name;
       this.Abbreviation = abbreviation;
    }
}

Once you've done this, you must inform NHibernate about the existance of this class. You do this by Importing the class in an hbm.xml file for instance . (I do not know how to do it using Fluent).

<import class="MyNamespace.TechnologyView" />

After that, you can create a query (using HQL or Criteria) which retrieves TechnologyView instances. NHibernate is smart enough to generate a performant SQL query.

using HQL:

IQuery q = s.CreateQuery ("select new TechnologyView (t.Id, t.Name, t.Abbreviation) from Technology t");

using Criteria:

ICriteria criteria = s.CreateCriteria (typeof(Technology));
criteria.SetResultTransformer (Transformers.AliasToBean (typeof(TechnologyView));
var result = criteria.List<TechnologyView>();
Frederik Gheysels
This does help so. One thing I want to clarify and maybe this is where I am getting lost. The reason I didn't want all of the data in the TechnologyDescription table for each of my Technologies was because some of the Descriptions would not be relevant for the page I am on. However, if I am understanding you correctly, then I would want to remove/disregard those results through how I cycle through the Technology Entity. That does strike me as odd though since that would mean there is no reason for Query strings as everything would be done after that.
RandomBen
I don't really quite understand your comment.If you don't need the descriptions at all on the page you're on, I would opt for the 'Transformations' approach.
Frederik Gheysels
+1  A: 

I think what you're looking for is for the TechnologyDescriptions to be lazy loaded. That way the descriptions get loaded from the database only when they are accessed (NHibernate will issue a second db query. Note that this can lead to N+1 selects in some situations and you might prefer the all at once query depending on the usage.)

By NHibernate xml mappings default to lazy loading of collections. In the past it seems that the Fluent NHibernate did not have the same default. You need to add .LazyLoad() to the mapping.

Recently it looks like lazy loading has become the default fluent mapping: http://stackoverflow.com/questions/316264/is-the-default-behavior-with-fluent-nhibernate-to-lazy-load-hasmanyt-collection

Jon Palmer