2

Suppose I have classes Animal, Cat and Dog. Cat and Dog are inherited from Animal. Consider following code:

var query = from animal in session.Linq<Animal>()
            where 
            animal.Color == "White"
            select animal;

How can I add a criteria to above query to query about type of entity? For example something like animal.Type == typeof(Cat).

Afshar Mohebi
  • 10,479
  • 17
  • 82
  • 126
  • couldn't you just say 'from animal in session.Linq()' ? – Pedro Nov 03 '10 at 17:47
  • If for some reason you can't do what Pedro is suggesting; have a look at my answer to this similar question: http://stackoverflow.com/questions/3451395/nhibernate-linq-nhibernate-queryexception-could-not-resolve-property-profile/3467077#3467077 – DanP Nov 03 '10 at 18:50
  • @Pedro: No, I can't. Because type of animal is not known until run time. – Afshar Mohebi Nov 03 '10 at 19:04
  • @DanP: I read that. But I can't change database. It's legacy. I can not add new column to existing tables. – Afshar Mohebi Nov 03 '10 at 19:08
  • You could likely do this in memory (if that's an option..not sure about the size of your datasets here)...something like: query.ToList().OfType(); Another option is to use a formula for the property (if there's a way you can use one to determine the type); could then query on this without changing the existing db. – DanP Nov 03 '10 at 19:20
  • 1
    @afsharm - How is NHibernate mapping to concrete classes of Cat and Dog if you don't have a discriminator column? (At least that's what I understand from your statement that you can't change the database.) If NHibernate can create Dog and Cat instances appropriately, then LINQ-to-NHibernate can perform a (from animal in session.Linq()) as Pedro suggested. Mappings and database schema would definitely help here. – James Kovacs Nov 03 '10 at 20:11
  • @James Kovacs: I'm using single table per class hierarchy inheritance. I have separate mapping per Animal, Cat and Dog. All that means I have discriminator column in Animal table. – Afshar Mohebi Nov 04 '10 at 05:45
  • @afshram - If that's true, why won't "from animal in session.Linq()" that Pedro suggested work? If it's not working, what is the error or problem with that solution? – James Kovacs Nov 04 '10 at 13:15
  • @James: "from animal in session.Linq()" works, but `Cat` is not known until runtime. In the other hand, type of Animal is one of search criterias based on user data put in UI. – Afshar Mohebi Nov 04 '10 at 13:18
  • @afsram - consider using my idea then...just map a read only property that uses a formula to determine the discriminator; you can then match against this in your linq query.... – DanP Nov 04 '10 at 16:52
  • @DanP, I used your way "a read only property that uses a formula ..." and it worked well. Please provide your comment as an answer so I could mark it as correct answer. – Afshar Mohebi Nov 15 '10 at 18:49
  • @afsram - glad to hear that worked for you, I've added my comment as an answer as requested. – DanP Nov 16 '10 at 15:20

3 Answers3

11

This is supported in the new provider:

var query = from animal in session.Query<Animal>()
            where animal.Color == "White" &&
                  animal is Cat
            select animal;
Diego Mijelshon
  • 52,548
  • 16
  • 116
  • 154
1

The version of LINQ-to-NHibernate that is compatible with NH 2.1.2 does not support querying by type at runtime.

// DOES NOT WORK WITH NH 2.1.2 & LINQ-to-NH
var desiredType = typeof(Cat);
var query = from animal in session.Linq<Animal>()
            where animal.Color == "White"
               && animal.GetType() == desiredType
            select animal;
// This results in an ArgumentOutOfRangeException in the LINQ provider

You can do this in-memory as suggested in the comments:

var desiredType = typeof(Cat);
var query = from animal in session.Linq<Animal>()
            where animal.Color == "White"
            select animal;
var animals = query.ToList();
var whiteCats = from animal in animals.AsQueryable()
                where animal.GetType() == desiredType
                select animal;

By performing query.ToList(), you are reading back all white animals and then performing the type-query using LINQ-to-Objects. (Depending on your exact query and mapping, you may have to worry about lazy proxies and therefore check if the object's type is assignable to the desiredType.) This does have the major disadvantage of reading back more data than needed in order to filter by animal type in memory. Depending on your domain and the query, this may or may not be a big issue.

If you cannot upgrade to NHibernate trunk (aka NH3), I would recommend not using LINQ-to-NHibernate for this query and instead using Criteria.

var desiredType = typeof(Cat);
var query = session.CreateCriteria(desiredType)
                   .Add(Restrictions.Eq("Color", "White")
                   .List<Animal>();

N.B. It should be obvious, but let me just state it explicitly. There is no reason that you can't use Criteria for this query and LINQ-to-NHibernate for your other queries. You can mix-an-match query techniques in NHibernate freely.

James Kovacs
  • 11,549
  • 40
  • 44
  • I like this solution because it offers a non-InMemory solution which also allows you to use 'typeof' instead of Generics. I never thought I'd end up using raw nHibernate Criteria again! – Holf Jul 16 '12 at 12:19
1

You can map a read-only property that uses your discriminator column as part of a formula. Querying on this column will allow you to distinguish between the types with the current nhcontrib provider.

Further guidance can be found in my answer to a similar question here.

Community
  • 1
  • 1
DanP
  • 6,310
  • 4
  • 40
  • 68