Как построить критерии NHibernate, чтобы найти родителей, у которых есть все указанные дети - PullRequest
2 голосов
/ 10 марта 2010

Проект может иметь много частей. Свойство Part является Ipn, представляющим собой строку цифр.

  • Проект "А" имеет части "1", "2", "3"
  • Проект "B" имеет части "2", "3", "4"
  • Проект "C" имеет детали "2"
  • Проект "D" имеет детали "3"

Я хочу найти все проекты, с которыми связаны все указанные части. Мой текущий запрос

            var ipns = new List<String> { "2", "3" }

            var criteriaForIpns = DetachedCriteria
                .For<Part>()
                .SetProjection(Projections.Id())
                .Add(Expression.In("Ipn", ipns));

            _criteriaForProject
                .CreateCriteria("Ipns")
                .Add(Subqueries.PropertyIn("Id", criteriaForIpns));

Это возвращает мне все проекты, в которых есть какие-либо части, поэтому набор результатов - это проекты A, B, C и D.

SQL, где генерируется предложение, выглядит примерно так:

WHERE    part1_.Id in (SELECT this_0_.Id as y0_
                   FROM   Parts this_0_
                   WHERE  this_0_.Ipn in ('2' /* @p0 */,'3' /* @p1 */))

Мой желаемый результат - это только проекты A и B. Как я могу построить критерии NHibernate, чтобы получить нужный мне набор результатов?

Количество деталей, которые я ищу, может варьироваться, это может быть n частей.

Ответы [ 2 ]

2 голосов
/ 05 января 2012

вчера я работал над подобной проблемой. Мне пришлось выбрать / загрузить все родительские объекты с точно заданным списком дочерних объектов. Я мог бы решить это с помощью Criteria-API с одним недостатком (см. * 1 ниже).

public class Project
{
  public virtual int ProjectId{get;set;}
  public virtual IList<Part> Parts{get;set;}
  ...
}    

public class Part
{
  public virtual int PartId{get;set;}
  public virtual Project Project{get;set;} // *1 this is the drawback: I need a public property for the ForegienKey from the child to the parent 
  ...
}

Вот критерии:

DetachedCriteria top = DetachedCriteria.For<Project>();
foreach(Part part in searchedParts)
{
  DetachedCriteria sub = DetachedCriteria.For<Part>();
  sub.Add(Expresion.Eq("PartId",part.PartId));
  sub.SetProjection("Project");
  top.Add(Subqueries.PropertyIn("ProjectId",sub));
}

Вернемся к вашему примеру: SQL будет выглядеть так.

SELECT * FROM project
WHERE 
    projectid IN ( SELECT projectid FROM part WHERE partid = 1 /* @p0 */ )
AND projectid IN ( SELECT projectid FROM part WHERE partid = 2 /* @p1 */ )

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

Привет

Juy Juka

Дополнительные виды использования

Я не закончил с моим кодом после этого, и если кому-то понадобится то, что я должен был выяснить, я добавлю его сюда. Я надеюсь, что дополнительная информация принадлежит здесь, но я не уверен, потому что это мой первый пост на stackoverflow.com

Для следующих примеров нам понадобится более сложный класс детали:

public class Part
{
  public virtual int PartId{get;set;}
  public virtual Project Project{get;set;}
  public virtual PartType PartType{get;set;}
  ...
}

public class PartType
{
  public virtual int PartTypeId{get;set;}
  public virtual string Name{get;set;}
  ...
}

Другой критерий для дочерних объектов

Можно использовать тот же код, если у вас нет первичного ключа (-ей) для поиска деталей, но вы хотите найти детали с другими свойствами.

// I am asuming building-projects with houses, gardens, garages, driveways, etc.
IEnumerable<PartType> searchedTypes = new PartType[]{housePart, gardenPart};
// could be a parameter or users choise or what ever

DetachedCriteria top = DetachedCriteria.For<Project>();
foreach(PartType type in searchedTypes)
{ 
  DetachedCriteria sub = DetachedCriteria.For<Part>();
  sub.Add(Expresion.Eq("PartType",type)); // this is all that had to be changed. We could even use more complex operations with and, or, not, etc.
  sub.SetProjection("Project");
  top.Add(Subqueries.PropertyIn("ProjectId",sub));
}

Ожидаемый SQL

SELECT * FROM project
WHERE 
    projectid IN ( SELECT projectid FROM part WHERE parttype = 1 /* @p0 // aka. housePart */ )
AND projectid IN ( SELECT projectid FROM part WHERE parttype = 2 /* @p1 // aka. gardenPart */ )

Исключая детей

Чтобы отрицать это и искать партнеров, которые не имеют искомые дочерние элементы, легко сделать, используя Subqueries.PropertyNotIn вместо Subqueries.PropertyIn.

Точно / только разыскиваемые дети

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

IEnumerable<PartType> searchedTypes = new PartType[]{housePart, gardenPart};
DetachedCriteria top = DetachedCriteria.For<Project>();
ICriterion notCriterion = null;
foreach(PartType type in searchedTypes)
{
  ICriterion subCriterion = Expresion.Eq("PartType",type);
  DetachedCriteria sub = DetachedCriteria.For<Part>();
  sub.Add(subCriterion); 
  sub.SetProjection("Project");
  top.Add(Subqueries.PropertyIn("ProjectId",sub));
  // I am collecting all valid criterions for child-objects and negate them
  subCriterion = Expresion.Not(subCriterion);
  notCriterion = notCriterion == null ? subCriterion:Expresion.And(notCriterion,subCriterion);
}
// with the negated criterions I exclude all parent-objects with an invalid child-object
DetachedCriteria not = DetachedCriteria.For<Part>();
not.Add(notCriterion);
sub.SetProjection("Project");
top.Add(Subqueries.PropertyNotIn("ProjectId",not));

Ожидаемый SQL

SELECT * FROM project
WHERE 
    projectid IN ( SELECT projectid FROM part WHERE parttype = 1 /* @p0 // aka. housePart */ )
AND projectid IN ( SELECT projectid FROM part WHERE parttype = 2 /* @p1 // aka. gardenPart */ )
AND projectid NOT IN ( SELECT projectid FROM part 
                       WHERE
                           NOT ( parttype = 1 /* @p2 // aka. housePart */ )
                       AND NOT ( parttype = 2 /* @p3 // aka. gardenPart */ )
                     )

(Возможно использование более одного дома и / или одного гвардейца, поскольку не делается пометка "дублированных" записей)

0 голосов
/ 10 марта 2010

Ваш запрос требует, чтобы мы сделали два соединения из проекта в деталь. Это невозможно в критериях.

HQL
Вы можете выразить этот запрос непосредственно в HQL.

var list = session.CreateQuery( @"
    select  proj from Project proj
        inner join proj.Parts p1
        inner join proj.Parts p2
    where   p1.Id=:id1
    and p2.Id=:id2
    " )
    .SetInt32( "id1", 2 )
    .SetInt32( "id2", 3 )
    .List<Master>();

Критерии
С помощью Criteria API вы бы запросили те проекты, которые имеют одну из указанных частей, и отфильтровали результаты в C #.

Либо есть критерии, загружающие Project.Parts, либо отобразите это как lazy = "extra".

Затем, используя существующий критерий запроса сверху.

// Load() these if necessary
List<Parts> required_parts;

var list = _criteriaForProject.List<Project>()
    .Where( proj => {
        foreach( var p in required_parts ) {
            if (!proj.Parts.Contains( p ))) {
                return false;
            }
            return true;
        }
    });

// if _criteriaForProject is a Detached Criteria, that would be:
var list = _criteriaForProject.GetExecutableCriteria( session )
    .List<Project>()
    .Where( // etc
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...