LINQ-NHibernate - выбор только нескольких полей (включая коллекцию) для сложного объекта - PullRequest
5 голосов
/ 05 января 2010

Я использую Fluent NHibernate в одном из моих проектов (и приложение ASP.NET MVC) с LINQ для запросов к данным (используя библиотеки LINQ to NHibernate).

Имена объектов изменены, чтобы защитить невинных.

Допустим, у меня есть следующие классы Foo, Bar, Baz и соответствующие им таблицы в базе данных (MySQL).

Foo имеет отношение «многие ко многим» и с Bar (таблица «FooBar»), и с Baz (таблица «FooBaz»), определенные в отображениях Fluent. Таким образом, интерфейс класса определяется следующим образом:

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; }
}

Это довольно стандартный класс. Мы видим, что у объекта Foo будет список баров и баз.

Проблема возникает при попытке выполнить запрос LINQ.

Если я делаю простой запрос, подобный этому, он отлично работает (предложение where неважно):

var foos = from foo in session.Linq<Foo>()
           where email.equals("foo@bar.com")
           select foo;

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

Это вернет список Foos со всеми заполненными полями (id, name, email, bars, bazes). log4net показывает, что NHibernate выполняет отдельные запросы для коллекций.

Проблема возникает, когда я хочу загрузить только некоторые поля. Например, я могу захотеть загрузить только столбцы в запросе, но не базы.

Этот запрос компилируется, но выдает ошибку во время выполнения:

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

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

Ошибка, которую я получаю - это что-то вроде индекса массива за исключением границ. Трассировка стека показывает имена некоторых методов, относящихся к обработке коллекции на стороне LINQ-NHibernate, но больше ничего. Запрос, представленный log4net, не показывает никаких признаков запроса на столбцах, что означает, что ошибка была обнаружена до того, как у него было время выполнить запрос для выбора столбцов.

У кого-нибудь еще была такая проблема? Как ты это решил? Я не хочу выбирать все объекты каждый раз, когда открываю веб-страницу в своем приложении!

Спасибо!

Редактировать: вот трассировка стека, как и было запрошено.

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

Ответы [ 4 ]

6 голосов
/ 05 января 2010

Пара предложений ...

Во-первых, NHibernate по умолчанию использует отложенную загрузку, поэтому может даже не потребоваться выполнять более «эффективный» запрос, который вы пытаетесь.

Во-вторых, следующий код не совсем делает то, что делает ваш пример кода - он создает список анонимных типов, называемых miniFoos, которые содержат подмножество полей в Foo. Но чистый эффект похож на то, что вы пытаетесь сделать. Он также использует то, что я считаю, называется синтаксис метода LINQ.

Я проверил это на объекте в моем собственном приложении, у которого также есть свойство IList, и оно работает (хотя я должен отметить, что в настоящее время я использую загрузку Eager). Я просто вырезал и вставил рабочий код и подставил названия вашего примера.

        using( var session = sessionFactory.OpenSession() )
        {
            session.BeginTransaction();

            var foos =
                session.CreateCriteria(typeof(Foo))
                       .List<Foo>();

            var miniFoos = 
               foos.Select(f => new { f.email, f.bars })
                   .Where(f => f.email.Equals("foo@bar.com)
                   .ToList();

            session.Close();
         }
2 голосов
/ 06 января 2010

Я уже пытался ответить на это, но мне это не нравится.

Чем больше я думаю о вашем вопросе, тем больше создается впечатление, что вы пытаетесь создать свою собственную версию Lazy Loading, которую, по моему опыту, довольно хорошо обрабатывает NHibernate.

Я знаю (из комментария), что вы переключились на Eager Loading, потому что вы не держали сеанс открытым. Я думаю, что в долгосрочной перспективе для вас будет более полезным, если вы сосредоточитесь на этом ракурсе.

Если я неправильно понял, возможно, вы могли бы отредактировать вопрос, чтобы объяснить, почему вы думаете, что вам нужно справиться с этим самостоятельно?

1 голос
/ 29 октября 2012

Мне удалось спроецировать не всю коллекцию, а ее поля

   ... 
   Select(a => new Forum {
   Id = a.Id, 
   Name = a.Name, 
   CategoryName = a.Categories.Select(b => b.Name).First() 
   })

CategoryName - это файл, зарегистрированный в форуме. который не имеет отображения с DB

В результате я получил небольшую проекцию огромного объекта форума и возможность сортировки на поле.

Надеюсь, что это кому-нибудь поможет.

0 голосов
/ 06 января 2010

я думаю, что вы можете решить свою проблему, определив преобразователь результатов и имена наборов результатов, если каким-либо образом вы можете получить доступ к Критериям, которые вы генерируете, вы можете избежать анонимного объекта.

проблема в том, чтоимена свойств и имена столбцов не совпадают, и при использовании проекции nhibernate / linq на nhibernate не проверяет, какое имя столбца и имя свойства.так что если вы определите их самостоятельно в свойствах результата (я думаю, что он называется набором результатов) в критериях, прежде чем выполнить запрос, он должен работать.

это довольно просто, попробуйте

...