Запрос NHibernate над проекцией - PullRequest
0 голосов
/ 25 декабря 2018

У меня есть две сущности A и B, где A имеет отношение один ко многим с B.Я хотел бы создать запрос NHibernate, который выбирает все A сущности со всеми B записями, где:

  • A сущность активна
  • выбранная B объектов находятся в пределах диапазона дат (у меня есть 2 c# DateTime объектов).
entity A
+----+--------+
| id | active |
+----+--------+
|  1 |      1 |
|  2 |      0 |
|  3 |      1 |
+----+--------+

entity B
+----+------+-------+------------+
| id | year | month | foreign_id |
+----+------+-------+------------+
|  1 | 2000 |    11 |          1 |
|  2 | 2001 |    12 |          2 |
|  3 | 2002 |     4 |          1 |
+----+------+-------+------------+

До сих пор я пробовал это:

return this.sessionFactory.GetCurrentSession()
    .QueryOver<A>()
    .Where(x => x.Active)
    .JoinQueryOver(x => x.BList)
    .WhereRestrictionOn(y => y.Year * 12 + y.Month) // the problem is here, exception below
    .IsBetween(2000 * 12 + 1) // january 2000
    .And(2010 * 12 + 3) // march 2010
    .List();

System.InvalidOperationException: variable 'x' of type 'Domain.A' referenced from scope '', but it is not defined

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

Я хотел бы помочь с исправлением этого фрагмента кода или посоветовать, как я могу сделать это лучше (или оба лучше).


Подробнеедетали:

Мои c# сущности выглядят так:

public class A
{
    public virtual int Id { get; set; }
    public virtual int Active { get; set; }
    public virtual IEnumerable<B> BList { get; set; }
}


public class B
{
    public virtual int Month { get; set; }
    public virtual int Year { get; set; }
}


internal class AMapping: ClassMap<A>
{
    public AMapping()
    {
        Table("AObjects");

        Id(x => x.Id, "id");
        Map(x => x.Active, "active");

        HasMany(x => x.BList)
            .Table("Bobjects")
            .KeyColumn("foreign_id")
            .Component(y => {
                  y.Map(b => b.Month, "month");
                  y.Map(b => b.Year, "year");
        });
    }
}

Ответы [ 2 ]

0 голосов
/ 07 января 2019

Основная проблема в вашем запросе - вы не можете использовать вычисления внутри WhereRestrictionOn:

.WhereRestrictionOn(y => y.Year * 12 + y.Month) // the problem is here, exception below

Здесь поддерживаются только сопоставленные свойства.Вы все еще можете написать требуемый запрос, но, похоже, вам нужно использовать вспомогательные методы подчеркивания ICriteria.Что-то вроде:

session.QueryOver<A>()
    .Where(x => x.Active)
    .JoinQueryOver(x => x.BList)
    .And(Restrictions.Between(
            Projections.SqlProjection("{alias}.Year * 12 + {alias}.Month", new string[] { }, new Type.IType[] { }),
            2000 * 12 + 1,
            2010 * 12 + 3))
    .List();
0 голосов
/ 25 декабря 2018

Я думаю, что путь здесь заключается в использовании фильтров.Первое, что нужно сделать, это создать определение фильтра с помощью класса фильтра, например:

public class MonthsFilter : FilterDefinition
{
    public MonthsFilter()
    {
        WithName("MonthsFilter")
            .AddParameter("startMonths", NHibernateUtil.Int32)
            .AddParameter("endMonths", NHibernateUtil.Int32);
    }
}

Затем вам нужно добавить фильтр к вашему ClassMap для A, к свойству BList,с помощью метода ApplyFilter:

internal class AMapping : ClassMap<A>
{
    public AMapping()
    {
        Table("AObjects");

        Id(x => x.Id, "id");
        Map(x => x.Active, "active");

        HasMany(x => x.BList)
            .Table("BObjects")
            .KeyColumn("foreign_id")
            .Component(y => {
                y.Map(b => b.Month, "month");
                y.Map(b => b.Year, "year");
            }).ApplyFilter<MonthsFilter>("year * 12 + month BETWEEN :startMonths and :endMonths");
    }
}

И, наконец, вам нужно включить фильтр перед выполнением запроса, аналогично следующему:

using (var session = sessionFactory.OpenSession())
{
    // Enable filter and pass parameters
    var startMonthsValue = 2000 * 12 + 1;    // january 2000
    var endMonthsValue = 2010 * 12 + 3;  // march 2010
    session.EnableFilter("MonthsFilter")
        .SetParameter("startMonths", startMonthsValue)
        .SetParameter("endMonths", endMonthsValue);

    // Create and execute query (no filter for B needed here)
    var list = session.QueryOver<A>()
        .Fetch(x => x.BList).Eager  // Eager fetch to avoid the N+1 problem due to BList lazy load
        .Where(x => x.Active)
        .TransformUsing(Transformers.DistinctRootEntity)    // List only distinct A entities, to avoid duplicated entries due to eager fetch one-to-many relation
        .List();

    // Do whatever you want with the results
    foreach (var item in list)
    {
        Console.WriteLine("A id: {0} - B children count: {1}", item.Id, item.BList.Count());
    }
}

Полный рабочий пример:

using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Mapping;
using NHibernate;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace NHibernateTests
{
    public class A
    {
        public virtual int Id { get; set; }
        public virtual bool Active { get; set; }
        public virtual IEnumerable<B> BList { get; set; }
    }


    public class B
    {
        public virtual int Month { get; set; }
        public virtual int Year { get; set; }
    }


    internal class AMapping : ClassMap<A>
    {
        public AMapping()
        {
            Table("AObjects");

            Id(x => x.Id, "id");
            Map(x => x.Active, "active");

            HasMany(x => x.BList)
                .Table("BObjects")
                .KeyColumn("foreign_id")
                .Component(y => {
                    y.Map(b => b.Month, "month");
                    y.Map(b => b.Year, "year");
                }).ApplyFilter<MonthsFilter>("year * 12 + month BETWEEN :startMonths and :endMonths");
        }
    }

    public class MonthsFilter : FilterDefinition
    {
        public MonthsFilter()
        {
            WithName("MonthsFilter")
                .AddParameter("startMonths", NHibernateUtil.Int32)
                .AddParameter("endMonths", NHibernateUtil.Int32);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var sessionFactory = CreateNHibernateSessionFactory();
            using (var session = sessionFactory.OpenSession())
            {
                // Enable filter and pass parameters
                var startMonthsValue = 2000 * 12 + 1;    // january 2000
                var endMonthsValue = 2010 * 12 + 3;  // march 2010
                session.EnableFilter("MonthsFilter")
                    .SetParameter("startMonths", startMonthsValue)
                    .SetParameter("endMonths", endMonthsValue);

                // Create and execute query (no filter needed here)
                var list = session.QueryOver<A>()
                    .Fetch(x => x.BList).Eager  // Eager fetch to avoid the N+1 problem due to BList lazy load
                    .Where(x => x.Active)
                    .TransformUsing(Transformers.DistinctRootEntity)    // List only distinct A entities, to avoid duplicated entries due to eager fetch one-to-many relation
                    .List();

                // Do whatever you want with the results
                foreach (var item in list)
                {
                    Console.WriteLine("A id: {0} - B children count: {1}", item.Id, item.BList.Count());
                }
            }

            Console.WriteLine("Press ENTER to continue...");
            Console.ReadLine();
        }

        static ISessionFactory CreateNHibernateSessionFactory()
        {
            FluentConfiguration fc = Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2012.ConnectionString("Server=.\\SQLEXPRESS;Database=NHibernateTests;Trusted_Connection=True;"))
                .Mappings(m => {
                    m.FluentMappings
                        .AddFromAssembly(Assembly.GetExecutingAssembly());
                });

            var config = fc.BuildConfiguration();

            return config.SetProperty(NHibernate.Cfg.Environment.ReleaseConnections, "on_close")
                       .BuildSessionFactory();
        }
    }
}

Дополнительная информация о фильтрах:

...