Possible Problem with Query Method in IRepository

14 views
Skip to first unread message

RalyDSM

unread,
Jan 5, 2009, 2:41:23 PM1/5/09
to Fluent NHibernate
I think I might have found a bug, but I'm not really sure. It could be
a syntax error on my part, but the compiler isn't catching. Anyway,
here is what I'm trying to do. Basically I've written my own
repository class that essentially just wraps the Fluent Repository
class. So here is the relevant code:


Public Class GenericRepository(Of T As IHasIntId)


Private _fluentRepos As FluentNHibernate.Framework.IRepository

Public Sub New(ByVal FluentRepository As
FluentNHibernate.Framework.IRepository)
_fluentRepos = FluentRepository
End Sub

Private Sub New()

End Sub

Public Function GetById(ByVal Id As Integer) As T

Return Query(Function(x As T) (x.Id = Id)).FirstOrDefault

End Function

Public Function Query(ByVal w As Expression(Of System.Func(Of T,
Boolean))) As IList(Of T)

Return _fluentRepos.Query(Of T)(w).ToList()

End Function

End Class

Then I wrote two unit tests, one that would pass in an
InMemoryRepository and one that would use an actual NHibernate session
to hit the real database.

here they are:

<TestMethod()> Public Sub InMemoryTest()

Dim inmemRepos As New InMemoryRepository()

Dim p As New Product()
Dim id As Integer = 5
p.Id = id
p.Title = "my product"

inmemRepos.Save(p)

Dim genRepos As New GenericRepository(Of Product)(inmemRepos)

Dim foundP = genRepos.GetById(id)

Assert.AreEqual(p.Title, foundP.Title)

End Sub

<TestMethod()> Public Sub DatabaseTest()

Dim session = NHibernateSessionManager.Instance.GetSession()

Dim flRepos As New Repository(session)
Dim genRepos As New GenericRepository(Of Product)(flRepos)

Dim id As Integer = 1

Dim p = genRepos.GetById(id)

Assert.IsNotNull(p)
Assert.AreEqual(id, p.Id)

End Sub

The InMemoryTest passed, and the DatabaseTest failed. The exception
from the DatabaseTest was a type conversion, from int to product (or
maybe the other way around.) I was able to "fix" it though. In the
Fluent NHibernate code I changed the Query method on the Repository
class from:

return _session.Linq<T>().Where(where).ToArray();

to

return _session.Linq<T>().Where(where.Compile()).ToArray();

Now both tests pass. All of the unit tests in the Fluent NHibernate
project pass either way.

James Gregory

unread,
Jan 5, 2009, 2:49:39 PM1/5/09
to fluent-n...@googlegroups.com
That's very interesting. Unfortunately, the bits in the Framework
project are rather neglected, so there may very-well be a bug in
there. I need to investigate this before I can pass judgement though.

Jeremy Skinner

unread,
Jan 5, 2009, 3:04:12 PM1/5/09
to fluent-n...@googlegroups.com
I don't think this is an issue with the Repository, rather an issue with Linq to NHibernate. The VB and C# compilers (unhelpfully) create expression trees differently. My guess is the Linq to NHibernate code does not parse VB expression trees correctly (I can't verify this as I don't have VB installed on this machine).

You don't want to be calling where.Compile() - this causes the expression tree to be compiled into a delegate and can no longer be translated into SQL - instead this ends up loading the entire table into your application and then doing an *in-memory* filter (bypassing Linq to NHibernate's expression tree parsing). 

Jeremy 

RalyDSM

unread,
Jan 5, 2009, 3:10:17 PM1/5/09
to Fluent NHibernate
so are you saying that I can't use VB then?

On Jan 5, 3:04 pm, "Jeremy Skinner" <jer...@jeremyskinner.co.uk>
wrote:
> I don't think this is an issue with the Repository, rather an issue with
> Linq to NHibernate. The VB and C# compilers (unhelpfully) create expression
> trees differently. My guess is the Linq to NHibernate code does not parse VB
> expression trees correctly (I can't verify this as I don't have VB installed
> on this machine).
>
> You don't want to be calling where.Compile() - this causes the expression
> tree to be compiled into a delegate and can no longer be translated into SQL
> - instead this ends up loading the entire table into your application and
> then doing an *in-memory* filter (bypassing Linq to NHibernate's expression
> tree parsing).
> Jeremy
> 2009/1/5 James Gregory <jagregory....@gmail.com>
>
>
>
> > That's very interesting. Unfortunately, the bits in the Framework
> > project are rather neglected, so there may very-well be a bug in
> > there. I need to investigate this before I can pass judgement though.
>

RalyDSM

unread,
Jan 5, 2009, 3:17:51 PM1/5/09
to Fluent NHibernate
are you SURE about the compile thing? When I turn on the show_sql
property in NHibernate this is the query that is written:
SELECT this_.Id as Id2_0_, this_.Title as Title2_0_ FROM [Product]
this_


On Jan 5, 3:04 pm, "Jeremy Skinner" <jer...@jeremyskinner.co.uk>
wrote:
> I don't think this is an issue with the Repository, rather an issue with
> Linq to NHibernate. The VB and C# compilers (unhelpfully) create expression
> trees differently. My guess is the Linq to NHibernate code does not parse VB
> expression trees correctly (I can't verify this as I don't have VB installed
> on this machine).
>
> You don't want to be calling where.Compile() - this causes the expression
> tree to be compiled into a delegate and can no longer be translated into SQL
> - instead this ends up loading the entire table into your application and
> then doing an *in-memory* filter (bypassing Linq to NHibernate's expression
> tree parsing).
> Jeremy
> 2009/1/5 James Gregory <jagregory....@gmail.com>
>
>
>
> > That's very interesting. Unfortunately, the bits in the Framework
> > project are rather neglected, so there may very-well be a bug in
> > there. I need to investigate this before I can pass judgement though.
>

Jeremy Skinner

unread,
Jan 5, 2009, 3:19:05 PM1/5/09
to fluent-n...@googlegroups.com
Yep, you'll notice there's no where clause on the query. Its loading the entire table then performing an in-memory linq query.

2009/1/5 RalyDSM <jo...@glmotorsports.net>

RalyDSM

unread,
Jan 5, 2009, 3:19:31 PM1/5/09
to Fluent NHibernate
actually, you're right. I re-read the statement and noticed that there
isn't a where clause in it.

RalyDSM

unread,
Jan 5, 2009, 3:26:49 PM1/5/09
to Fluent NHibernate
so I guess i need to take this problem to the linq-to-nhibernate
guys...

On Jan 5, 3:19 pm, "Jeremy Skinner" <jer...@jeremyskinner.co.uk>
wrote:
> Yep, you'll notice there's no where clause on the query. Its loading the
> entire table then performing an in-memory linq query.
>
> 2009/1/5 RalyDSM <j...@glmotorsports.net>

Jeremy Skinner

unread,
Jan 5, 2009, 3:31:39 PM1/5/09
to fluent-n...@googlegroups.com
I can't be certain as I haven't got VB installed to test this, but it certainly appears to be the case. Linq to NHibernate's unit tests are all in C# so it doesn't surprise me that the differences in VB's expression trees have been missed. 

2009/1/5 RalyDSM <jo...@glmotorsports.net>

Josh Pollard

unread,
Jan 6, 2009, 6:00:48 PM1/6/09
to Fluent NHibernate
I did some more investigating and it looks like Linq2NHib only pukes
when using lambda expressions in VB. The following works:

Dim product = (From p In session.Linq(Of Product)() _
Where p.Id = testId _
Select p).FirstOrDefault()

Obviously that isn't using Fluent NHib at all. So the question is, how
do I use Fluent NHib without using lambdas? I think the problem is
that I don't know what us as my "in"

Any ideas on how to cram a full linq query into the Fluent NHib Query
method?

On Jan 5, 3:31 pm, "Jeremy Skinner" <jer...@jeremyskinner.co.uk>
wrote:
> I can't be certain as I haven't got VB installed to test this, but it
> certainly appears to be the case. Linq to NHibernate's unit tests are all in
> C# so it doesn't surprise me that the differences in VB's expression trees
> have been missed.
>
> 2009/1/5 RalyDSM <j...@glmotorsports.net>

James Gregory

unread,
Jan 7, 2009, 5:42:22 PM1/7/09
to fluent-n...@googlegroups.com
Josh: I've tried reproducing your problem, but I haven't been able to. I created a Product class, a ProductMap, and then wrote a little unit test to see if it worked, and it did.

<TestFixture()> _
Public Class VbLinqTests
    Private _source As ISessionSource

    <SetUp()> _
    Public Sub CreateDatabase()
        Dim properties As IDictionary(Of String, String) = New SQLiteConfiguration() _
            .UseOuterJoin() _
            .InMemory() _
            .ToProperties()

        _source = New SingleConnectionSessionSourceForSQLiteInMemoryTesting(properties, New TestModel())
        _source.BuildSchema()
    End Sub

    <Test()> _
    Public Sub TestLinq()
        Dim flRepos As New Repository(_source)

        flRepos.Save(New Product())
        flRepos.Save(New Product())
        flRepos.Save(New Product())
        flRepos.Save(New Product())

        Dim product As Product = flRepos.Query(Of Product)(Function(p As Product) p.Id > 1).ToList().FirstOrDefault

        Assert.NotNull(product)
    End Sub
End Class

Can you try running that yourself to see what happens?

Josh Pollard

unread,
Jan 7, 2009, 11:52:35 PM1/7/09
to Fluent NHibernate
well now i feel dumb and bad. first off, i wasnt able to get your
example to compile, but it was probably more due to my lack of
knowledge around sqlite than anything. so i decided to write another
unit test of my own that basically looks like this:


Dim flRepos As New Repository(session)
Dim id As Integer = 1
Dim product = flRepos.Query(Of Product)(Function(p As Product)
p.Id = id).FirstOrDefault()

Assert.AreEqual(id, product.Id)

and it passed. so the problem doesnt look like its in fluent
nhibernate from that stand point. But if you look up at my original
question, you'll see that I'm trying to wrap the fluent nhibernate
repository in my own generic repository. When I then call the GetById
method, which I believe does the same thing as what I have in the code
in this message, I get an exception trying to cast Int to Product.

Does that make sense?

James Gregory

unread,
Jan 8, 2009, 5:42:05 AM1/8/09
to fluent-n...@googlegroups.com
I'll take a look at your GenericRepository, see if I can replicate the issue.

James Gregory

unread,
Jan 8, 2009, 6:29:59 AM1/8/09
to fluent-n...@googlegroups.com
Right, after a lot more experimentation I've come to the conclusion that it's to do with generic type parameters. I've basically extracted the logic out of your GenericRepository, reducing it until I can replicate the issue in the smallest possible form. Take the following test:

    <Test()> _
    Public Sub TestLinq()
        Dim flRepos As New Repository(_source)

        flRepos.Save(New Product())
        flRepos.Save(New Product())

        flRepos.Query(Function(x As Product) (x.Id = 2))
        GetById(Of Product)(2, flRepos)
    End Sub

    Public Function GetById(Of T As IHasIntId)(ByVal Id As Integer, ByVal flRepos As IRepository) As T()
        Return flRepos.Query(Function(x As T) (x.Id = 2))
    End Function

There's a direct Query call on the repository, then there's a call to the same method inside a helper method that uses a generic type. The direct Query call succeeds, but the GetById call fails.

If you change the helper method to this:

    Public Function GetById(Of T As IHasIntId)(ByVal Id As Integer, ByVal flRepos As IRepository) As Product()
        Return flRepos.Query(Function(x As Product) (x.Id = 2))
    End Function

Then it suddenly works. Notice the T is not actually used and it's hard coded to Product.

My main conclusion is that this is definitely not a Fluent NHibernate issue. It's something wrong with how Linq to NHibernate handles VBs generic parameters.

A workaround, and it's not a nice one, is to either have a repository per type, or create your generic repository in C# (I tried this and it works without any trouble).
Reply all
Reply to author
Forward
0 new messages