Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ-NHibernate - Selecting only a few fields (including a Collection) for a complex object

I'm using Fluent NHibernate in one of my projects (and ASP.NET MVC application), with LINQ to query to data (using the LINQ to NHibernate libraries).

The objet names are changed to protect the innocent.

Let's say I have the following classes Foo, Bar, Baz, and their appropriate tables in the database (MySQL).

Foo has a many-to-many relationship with both Bar (table "FooBar") and Baz (table "FooBaz"), defined in the Fluent mappings. As such, the class interface is defined as follows:

public class Foo {
    public virtual int id { get; set; }
    public virtual string name { get; set; }
    public virtual string email { get; set; }
    public virtual IList<Bar> bars { get; set; }
    public virtual IList<Baz> bazes { get; set; }
}

This is a pretty standard class. We can see that a Foo object will have a list of bars and bazes.

The problem comes when trying to do a LINQ query.

If I do a simple query like this, it works fine (the where clause is unimportant) :

var foos = from foo in session.Linq<Foo>()
           where email.equals("[email protected]")
           select foo;

IList<Foo> listFoos = foos.ToList();

This will return a list of Foos, with all the fields populated (id, name, email, bars, bazes). log4net shows that NHibernate performs separate queries for the collections.

The problem arises when I want to load only some fields. For example, I might want to load only the bars in the query, but not the bazes.

This query compiles, but produces an error at runtime:

var foos = from foo in session.Linq<Foo>()
           where email.equals("[email protected]")
           select new Foo() 
           {
               id = foo.id,
               name = foo.name,
               email = foo.email,
               bars = foo.bars
           };

IList<Foo> listFoos = foos.ToList();

The error I get is something along the lines of an array index out of bounds exception. The stack trace shows some methods names relating the collection handling on LINQ-NHibernate's side, but nothing else. The query reported by log4net shows no sign of a query on bars, which means than an error was caught before it had the time to perform the query to select the bars.

Did anyone else had this kind of problem before? How did you solve it? I don't want to have to select all the objects everytime I open a web page in my application!

Thank you!

Edit: here is the stacktrace, as requested.

System.IndexOutOfRangeException: L'index se trouve en dehors des limites du tableau. (read: "Index is out of bounds for the array.")

[IndexOutOfRangeException: L'index se trouve en dehors des limites du tableau.]
   NHibernate.Transform.TypeSafeConstructorMemberInitResultTransformer.InvokeMemberInitExpression(MemberInitExpression expression, Object[] args, Int32& argumentCount) +404
   NHibernate.Transform.TypeSafeConstructorMemberInitResultTransformer.TransformTuple(Object[] tuple, String[] aliases) +150

[QueryException: could not instantiate: Foo]
   NHibernate.Transform.TypeSafeConstructorMemberInitResultTransformer.TransformTuple(Object[] tuple, String[] aliases) +265
   NHibernate.Loader.Criteria.CriteriaLoader.GetResultColumnOrRow(Object[] row, IResultTransformer resultTransformer, IDataReader rs, ISessionImplementor session) +171
   NHibernate.Loader.Loader.GetRowFromResultSet(IDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters, LockMode[] lockModeArray, EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, Boolean returnProxies) +330
   NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) +704
   NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) +70
   NHibernate.Loader.Loader.DoList(ISessionImplementor session, QueryParameters queryParameters) +111
   NHibernate.Loader.Loader.ListIgnoreQueryCache(ISessionImplementor session, QueryParameters queryParameters) +18
   NHibernate.Loader.Loader.List(ISessionImplementor session, QueryParameters queryParameters, ISet`1 querySpaces, IType[] resultTypes) +79
   NHibernate.Impl.SessionImpl.List(CriteriaImpl criteria, IList results) +407
   NHibernate.Impl.CriteriaImpl.List(IList results) +41
   NHibernate.Impl.CriteriaImpl.List() +35
   NHibernate.Linq.<GetEnumerator>d__0.MoveNext() +71
   System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) +7665172
   System.Linq.Enumerable.ToList(IEnumerable`1 source) +61
   FooRepository.List(Int32 count) in C:\...\FooRepository.cs:38
   FooController.List() in C:\...\FooController.cs:30
   lambda_method(ExecutionScope , ControllerBase , Object[] ) +39
   System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +17
   System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +178
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +24
   System.Web.Mvc.<>c__DisplayClassa.<InvokeActionMethodWithFilters>b__7() +52
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation) +254
   System.Web.Mvc.<>c__DisplayClassc.<InvokeActionMethodWithFilters>b__9() +19
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +192
   System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +399
   System.Web.Mvc.Controller.ExecuteCore() +126
   System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +27
   System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +7
   System.Web.Mvc.MvcHandler.ProcessRequest(HttpContextBase httpContext) +151
   System.Web.Mvc.MvcHandler.ProcessRequest(HttpContext httpContext) +57
   System.Web.Mvc.MvcHandler.System.Web.IHttpHandler.ProcessRequest(HttpContext httpContext) +7
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +181
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +75
like image 839
Guillaume Gervais Avatar asked Jan 05 '10 15:01

Guillaume Gervais


1 Answers

I tried answering this already, but am not happy with it.

The more I think about your question, the more it seems like you are trying to do your own version of Lazy Loading, which NHibernate handles pretty well on it's own, in my experience.

I know (from a comment) that you switched to Eager Loading because you were not keeping a session open. I think it would be more useful for you in the long run if you focused on that angle.

If I've misunderstood, perhaps you could edit the question to explain why you think you need to handle it yourself?

like image 155
Tom Bushell Avatar answered Oct 31 '22 19:10

Tom Bushell