I have Hierarchical datatable for filling TreeList in VB.Net like following:
ID ParentID Name
----------------------
1 NUll a
2 NUll b
3 2 c
4 1 d
5 3 e
6 5 f
7 6 g
8 5 h
My question:
How Can get list of all children for a node(ID) with Linq in vb.net?
Please help me.
Unfortunately I don't know VB good enough to give you a VB answer. I'll give the answer in C#. You'll probably understand the Idea. Maybe you can add your VB-translation?
You forgot to describe your Node class. I assume you do not only want the Children, but also the Grandchildren (etc).
class TreeNode
{
public int Id {get; set;}
public string Name {get; set;}
// every node has zero or more sub-nodes:
public IEnumerable<TreeNode> Nodes {get; set;}
}
You want your hierarchy to an unknown level deep. Hence you can't use the Standard LINQ functions. But you can easily extend LINQ to create your own. See Extension Function demystified
public static IEnumerable<TreeNode> AsTreeNodes(this IEnumerable<Person> persons)
{
// Top TreeNodes: all persons without a Parent:
return persons.AsTreeNodes((int?)null);
}
The actual function that returns the sequence of Nodes, of all persons with parentId, using recursion:
public static IEnumerable<TreeNode> AsTreeNodes(this IEnumerable<Person> persons, int? parentId)
{
// Top Nodes: all persons with parentId
var personsWithParentId = persons.Where(person.ParentId == parentId);
foreach (var person in personsWithParentId)
{
// every person will become one TreeNode with sub-nodes using recursion
TreeNode node = new TreeNode()
{
Id = person.Id,
Name = person.Name,
// get all my Children and Children's children:
Nodes = persons.ToNodeCollection(person.Id),
};
yield return node;
}
}
Note that I choose to return IEnumerable instead of List / Array. This way items will only be created when you really enumerate over them. So if you don't want to use the Node of the person with Id 1, all his children will not be created.
Usage: get the family hierarchy of all descendants of "George Washington":
IEnumerable<Person> allPersons = ...
IEnumerable<TreeNode> allFamilyHierarchies = allPersons.AsTreeNodes();
IEnumerable<TreeNode> washingtonFamily = allFamilyHierarchies
.Where(familyHierarchy => familyHierarchy.Name == "George Washington");
Note: until now, no TreeNode has been created, only the IEnumerable has been created.
The enumeration starts as soon as you do something that does not return IEnumerable,
like foreach, ToList, Any, FirstOrDefault.
Hence all non-Washington families will be ignored, no Top nodes, nor sub-nodes for them will be created.
If you look closely, then you'll see that to find all Children I have to enumerate over the complete collection, to find all Persons with a certain ParentId. This can be done way more efficiently if you first group all persons into groups with same ParentId and then put these groups into a Dictionary with the ParentId as Key. This way it will be very fast to find all Children of the parent with Id X:
var personsWithSameParentId = persons.GroupBy(person => person.ParentId);
var dictionary = personsWithSameParentId.ToDictionary(group => group.Key)
Every element in the dictionary has key equal to parentId and as elements all persons with this parentId.
TreeNode CreateNodeForPerson(Person person)
{
IEnumerable<Person> children = dictionary[person.Id];
IEnumerable<TreeNode> childNodes = children
.Select(child => CreateNodeforPerson(child));
return new TreeNode()
{
Id = person.Id,
Name = person.Name,
Nodes = childNodes,
};
}
You'll see the same recursion. But once you have the dictionary, you don't have to enumerate over the complete Person collection, you only access the children / children's children etc of the Person you are creating the nod for.
Related
I am looking for help with a LINQ SQL query please.
I have a blazor application that gets data from an Azure SQL database. I am seeking to get a dataset from the database for linking to a datagrid, where each row is a record from the main table joined with a record from the second table. The second table has millions of records, it needs to join one record which has the same key (securityId) and with the date being the record with the nominated date, or with the last date before the nominated date.
Because of the size of the 2nd file, I need an efficient query. Currently I am using the following, but I believe there must be more efficient ways to do it without the lag. Also tried Navigation Properties but couldn't get to work
reviewdateS is the date that I want the 2nd record to match or be the latest date prior to that date
result = (from cmpn in _ctx.MstarCompanies
join prcs in _ctx.MstarPrices
on cmpn.securityId equals prcs.securityId into cs
from c in cs.DefaultIfEmpty()
where c.date01 == reviewDateS
select new ClsMarketPrices { })
Following are the 3 relevant classes. ClsMarketPrices does not relate to a database table, it is simple a class that combines the other 2 classes which may not be necessary but with my limited knowledge it is how it is working.
_ctx is a repository that links to the data context.
public MySQLRepositories(ApplicationDbContext ctx)
{
_ctx = ctx;
}
public class ClsMarket
{
[Key]
public int CompanyId { get; set; } = 0;
public string securityId { get; set; } = "";
public string companyName { get; set; } = "";
public string mic { get; set; } = "";
public string currency { get; set; } = "";
[ForeignKey("securityId")]
public virtual ICollection<ClsPrices> Prices { get; set; }
}
public class ClsMarketPrices
{
[Key]
public int CompanyId { get; set; } = 0;
public string companyName { get; set; } = "";
public string period { get; set; } = "";
public string mic { get; set; } = "";
}
public class ClsPrices
{
[Key]
public int PricesId { get; set; }
[ForeignKey("securityId")]
public string securityId { get; set; } = "";
public string mic { get; set; } = "";
public string date01 { get; set; } = "";
public virtual ClsMarket ClsMarket {get; set;}
}
I want to get a record from the 1st file joined with a record from the 2nd file where that record from the 2nd file has a date equal to or the last before the nominated date.
So we are talking about files, not a database! This is important, because this means that your local process will execute the LINQ, not a database management system. In other words: the LINQ will be IEnumerable, not IQueryable.
This is important, because as Enumerable, you will be able to define your own LINQ extension methods.
Although you supplied an enormous amount of irrelevant properties, you forgot to give us the most important things: you were talking about two files, you told us that you have two classes with a one-to-many relation, but you gave us three classes. Which ones do have the relation that you are talking about?
I think that every object of ClsMarketPrices has zero or more ClsPrices, and that every ClsPrice is one of the prices of a ClsMarketPrices, namely the ClsMarketPrices that the foreign key SecurityId (rather confusing name) refers to.
First of all, let's assume you already have procedures to read the two sequences from your files. And of course, these procedures won't read more than needed (so don't read the whole file if you will only use the first ClsMarket). I assume you already know how to do that:
IEnumerable<ClsMarketPrices> ReadMarketPrices();
IEnumerable<ClsPrices> ReadPrices();
So you've go a DateTime reviewDate. Every MarketPrice has zero or more Prices. Every Price has a DateTime property DateStamp. You want for every MarketPrice the Price that has the largest value for DateStamp that is smaller or equal to reviewDate.
If a MarketPrice doesn't have such a Prices, for instance because it doesn't have a Price at all, or all its Prices have a DateStamp larger than reviewDate, you want a value null.
You didn't say what you want if a MarketPrice has several Prices with equal largest DateStamp <= reviewDate. I assume that you don't care which one is selected.
The straighforward LINQ method would be to use GroupJoin, Where, Orderby and FirstOrDefault:
DateTime reviewDate = ...
IEnumerable<ClsMarketPrices> marketPricess = ReadMarketPrices();
IEnumerable<ClsPrices> prices = ReadPrices().Where(price => price.DateStamp <= reviewDate);
// GroupJoin marketPrices with prices:
var result = markets.GroupJoin(prices,
marketPrice => marketPrice.CompanyId, // from every MarketPrice take the primary key
price => price.CompanyId, // from every price take the foreign key to its market
// parameter resultSelector: from every market, with its zero or more matching prices
// make one new:
(marketPrice, pricesOfThisMarketPrice) => new
{
// select the marketPrice properties that you plan to use:
Id = marketPrice.CompanyId,
Name = ...
...
// from all prices of this marketPrice, take the one with the largest DateStamp
// we know there are no marketPrices with a DataStamp larger than reviewData
LatestPrice = pricesOfThisMarketPrice.OrderbyDescending(price => price.DateStamp)
.Select(price => new
{
// Select the price properties you plan to use;
Id = price.PricesId,
Date = price.DateStamp,
...
})
.FirstOrDefault(),
});
The problem is: this must be done efficiently, because you have an immense amount of Markets and MarketPrices.
Althoug we already limited the amount of prices to sort by removing the prices that are after reviewDate, it is still a waste to order all Dates if you will only be using the first one.
We can optimize this, by using Aggregate for pricesOfThisMarketPrice. This will assert that pricesOfThisMarketPrice will be enumerated only once.
Side remarks: Aggregate only works on IEnumerable, not on IQueryable, so it won't work on a database. Furthermore, pricesOfThisMarketPrice might be an empty sequence; we have to take care of that.
LatestPrice = pricesOfThisMarketPrice.Any() ?
pricesOfThisMarketPrice.Aggregate(
// select the one with the largest value of DateStamp:
(latestPrice, nextPrice) => nextPrice.DateStamp >= latesPrice.DateStamp) ? nextPrice : latestPrice)
// do not do the aggregate if there are no prices at all:
: null,
Although this Aggregate is more efficient than OrderBy, your second sequence will still be enumerated more than once. See the source code of Enumerable.GroupJoin.
If you really want to enumerate your second source once, and limit the number of enumerations of the first source, consider to create an extension method. This way you can use it as any LINQ method. If you are not familiar with extension methods, see extension methods demystified.
You can create an extension method for your ClsPrices and ClsPrice, however, if you think you will need to "find the largest element that belongs to another element" more often, why not create a generic method, just like LINQ does.
Below I create the most extensive extension method, one with a resultSelector and equalityComparers. If you will use standard equality, consider to add an extension method without these comparers and let this extension method call the other extension method with null value for the comparers.
For examples about the overloads with and without equality comparers see several LINQ methods, like ToDictionary: there is a method without a comparer and one with a comparer. This first one calls the second one with null value for comparer.
I will use baby steps, so you can understand what happens.
This can slightly be optimized.
The most important thing is that you will enumerate your largest collection only once.
IEnumerable<TResult> TakeLargestItem<T1, T2, TKey, Tproperty, TResult>(
this IEnumerable<T1> t1Sequence,
IEnumerable<T2> t2Sequence,
// Select primary and foreign key:
Func<T1, TKey> t1KeySelector,
Func<T2, TKey> t2KeySelector,
// Select the property of T2 of which you want the largest element
Func<T2, TProperty> propertySelector,
// The largest element must be <= propertyLimit:
TProperty propertyLimit,
// From T1 and the largest T2 create one TResult
Func<T1, T2, TResult> resultSelector,
// equality comparer to compare equality of primary and foreign key
IEqualityComparer<TKey> keyComparer,
// comparer to find the largest property value
IComparer<TProperty> propertyComparer)
{
// TODO: invent a property method name
// TODO: decide what to do if null input
// if no comparers provided, use the default comparers:
if (keyComparer == null) keyComparer = EqualityComparer<TKey>.Default;
if (propertyComparer == null) propertyComparer = Comparer<TProperty>.Default;
// TODO: implement
}
The implementation is straightforward:
put all T1 in a dictionary t1Key as key, {T1, T2} as value, keyComparer as comparer
then enumerate T2 only once.
check if the property <= propertyLimit,
if so, search in the dictionary for the {T1, T2} combination with the same key
check if the current t2Item is larger than the T2 in the {T1, T2} combination
if so: replace
We need an internal class:
class DictionaryValue
{
public T1 T1 {get; set;}
public T2 T2 {get; set;}
}
The code:
IDictionary<TKey, DictionaryValue> t1Dict = t1Sequence.ToDictionary(
t1 -> t1KeySelector(t1),
t1 => new DictionaryValue {T1 = t1, T2 = (T2)null },
keyComparer);
The enumeration of t2Sequence:
foreach (T2 t2 in t2Sequence)
{
// check if the property is <= propertyLimit
TProperty property = propertySelector(t2);
if (propertyComparer.Compare(property, propertyLimit) < 0)
{
// find the T1 that belongs to this T2:
TKey key = keySelector(t2);
if (t1Dict.TryGetValue(key, out DictionaryValue largestValue))
{
// there is a DictionaryValue with the same key
// is it null? then t2 is the largest
// if not null: get the property of the largest value and use the
// propertyComparer to see which one of them is the largest
if (largestValue.T2 == null)
{
largestValue.T2 = t2;
}
else
{
TProperty largestProperty = propertySelector(largestValue.T2);
if (propertyComparer.Compare(property, largestProperty) > 0)
{
// t2 has a larger property than the largestValue: replace
largestValue.T2 = t2,
}
}
}
}
}
So for every t1, we have found the largest t2 that has a property <= propertyLimit.
Use the resultSelector to create the results.
IEnumerable<TResult> result = t1Dict.Values.Select(
t1WithLargestT2 => resultSelector(t1WithLargestT2.T1, t1WithLargestT2.T2));
return result;
I am using straight up Lucene (no Solr or ElasticSearch) to index a set of documents of which follow a parent-child hierarchy.
I am using 'blocks' to accomplish this by adding all children followed by the parent to the same block calling:
writer.addDocuments(childrenAndParentDocList)
I am doing a free text search across all parents and children (using ToParentBlockJoinQuery in the child search to link up to the parent docs), which is returning a nice set of parent Documents that either match the query, or which have a child which matches the query.
The next thing I need to do is to fetch all children for all of the parent documents that I have.
I have seen a method in a lucene test here, which shows how to get the parent doc, given a child doc.
private Document getParentDoc(IndexReader reader, BitSetProducer parents, int childDocID) throws IOException {
final List<LeafReaderContext> leaves = reader.leaves();
final int subIndex = ReaderUtil.subIndex(childDocID, leaves);
final LeafReaderContext leaf = leaves.get(subIndex);
final BitSet bits = parents.getBitSet(leaf);
return leaf.reader().document(bits.nextSetBit(childDocID - leaf.docBase));
}
But I'm unsure of how to do the opposite. i.e. how to fetch all children given a parent doc.
Any advice would be appreciated.
I've ended up using the code below. And it seems to work:
private List<Integer> getChildDocIds(IndexSearcher indexSearcher, int parentDocId) throws IOException {
//Use a query in QueryBitSetProducer constructor which identifies parent docs
BitSetProducer parentsFilter = new QueryBitSetProducer(new TermQuery(new Term("child", "N")));
IndexReader indexReader = indexSearcher.getIndexReader();
List<LeafReaderContext> leaves = indexReader.leaves();
int subIndex = ReaderUtil.subIndex(parentDocId, leaves);
LeafReaderContext leaf = leaves.get(subIndex);
int localParentDocId = parentDocId - leaf.docBase;
List<Integer> childDocs = new ArrayList<>();
if (localParentDocId == 0) {
//not a parent, or parent has no children
return childDocs;
}
int prevParent = parentsFilter.getBitSet(leaf).prevSetBit(localParentDocId - 1);
for(int childDocIndex = prevParent + 1; childDocIndex < localParentDocId; childDocIndex++) {
childDocs.add(leaf.docBase + childDocIndex);
}
return childDocs;
}
Base on your answer I made a c# port of the function, it also seems to work.
I only needed to get the nested documents without any filters or quires.
private List<Lucene.Net.Documents.Document> GetNestedDocuments(IndexSearcher searcher, int parentDocId)
{
List<Lucene.Net.Documents.Document> documents = new List<Lucene.Net.Documents.Document>();
int subIndex = ReaderUtil.SubIndex(parentDocId, searcher.IndexReader.Leaves);
var leaf = searcher.IndexReader.Leaves[subIndex];
if (parentDocId > leaf.DocBase)
{
for (var childDocIndex = leaf.DocBase; childDocIndex < parentDocId; childDocIndex++)
{
var childDoc = searcher.Doc(childDocIndex);
documents.Add(childDoc);
}
}
return documents;
}
I want to fetch Hierarchical/Tree data something like below from a Table which has following definiton.
Tree Table:
"""""""""""
Id |ParentId
"""""""""""
Work1|null
Work2|Work1
Work3|Work2
...
Required Query result Data (no need to be tabbed)- If I Pick 'Work1' I should complete Ids which are under its root something like below. If I pick 'Work2' then also I should complete Ids above and below its root.
> Work1
----------
> Work2
----------
> Work3
---------
What is the best way in NHibernate to fetch data in the above scenario in optimized manner.
To find out what the "best way" is, more information regarding the actual scenario would be needed. What kind of "optimization" are you looking for? Minimal amount of data (only the rows you are really going to need) or minimal number of SQL queries (preferably one roundtrip to the database) or any other?
Scenario 1: Menu or tree structure that is loaded once and kept in memory for longer periods of time (not a list that updates every few seconds). Small number of rows in the table (small is relative but I'd say anything below 200).
In this case I would just get the whole table with one query like this:
var items = session.Query<Work>()
.Fetch(c => c.ParentWork)
.Fetch(c => c.ChildWorks).ToList();
var item = session.Get<Work>(id);
This will result in a single SQL query which simply loads all the rows from the table. item will contain the complete tree (parents, grandparents, children, etc.).
Scenario 2: Large number of rows and only a fraction of rows needed. Only few levels in the hierarchy are to be expected.
In this case, just load the item and let NHibernate to the rest with lazy loading or force it to load everything by writing a recursive method to traverse parents and children. This will cause a N+1 select, which may or may not be slower than scenario 1 (depending on your data).
Here is a quick hack demonstrating this:
var item = session.Get<Work>(id);
Work parent = item.ParentWork;
Work root = item;
// find the root item
while (parent != null)
{
root = parent;
parent = parent.ParentWork;
}
// scan the whole tree
this.ScanChildren(root);
// -----
private void ScanChildren(Work item)
{
if (item == null)
{
return;
}
foreach (Work child in item.ChildWorks)
{
string name = child.Name;
this.ScanChildren(child);
}
}
Edit:
Scenario 3: Huge amount of data. Minimal number of queries and minimal amount of data.
In this case, I would think not of a tree structure but of having layers of data that we load one after another.
var work = repo.Session.Get<Work>(id);
// get root of that Work
Work parent = work.ParentWork;
Work root = work;
while (parent != null)
{
root = parent;
parent = parent.ParentWork;
}
// Get all the Works for each level
IList<Work> worksAll = new List<Work>() { root };
IList<Work> worksPerLevel = new List<Work>() { root };
// get each level until we don't have any more Works in the next level
int count = worksPerLevel.Count;
while (count > 0)
{
worksPerLevel = this.GetChildren(session, worksPerLevel);
// add the Works to our list of all Works
worksPerLevel.ForEach(c => worksAll.Add(c));
count = worksPerLevel.Count;
}
// here you can get the names of the Works or whatever
foreach (Work c in worksAll)
{
string s = c.Name;
}
// this methods gets the Works in the next level and returns them
private IList<Work> GetChildren(ISession session, IList<Work> worksPerLevel)
{
IList<Work> result = new List<Work>();
// get the IDs for the works in this level
IList<int> ids = worksPerLevel.Select(c => c.Id).ToList();
// use a WHERE IN clause do get the Works
// with the ParentId of Works in the current level
result = session.QueryOver<Work>()
.Where(
NHibernate.Criterion.Restrictions.InG<int>(
NHibernate.Criterion.Projections.Property<Work>(
c => c.ParentWork.Id),
ids)
)
.Fetch(c => c.ChildWorks).Eager // this will prevent the N+1 problem
.List();
return result;
}
This solution will not cause a N+1 problem, because we use an eager load for the children, so NHibernate will know the state of the child lists and not hit the DB again. You will only get x+y selects, where x is the number of selects to find the root Work and y is the number of levels (max depth of he tree).
I have a webservice I call from a WP7 app. I get a list of high scores in a table (name/score).. What is the simpliest way to add a 3rd column on the far left which is simply the row?
Do I need to add a property to the entity? Is there someway to get the row #?
I tried these things below with no success..
[OperationContract]
public List<DMHighScore> GetScores()
{
using (var db = new DMModelContainer())
{
// return db.DMHighScores.ToList();
var collOrderedHighScoreItem = (from o in db.DMHighScores
orderby o.UserScore ascending
select new
{
o.UserName,
o.UserScore
}).Take(20);
var collOrderedHighScoreItem2 = collOrderedHighScoreItem.AsEnumerable().Select((x, i) => new DMHighScoreDTO
{
UserName = x.UserName,
UserScore = x.UserScore
}).ToList();
}
}
[DataContract]
public class DMHighScoreDTO
{
int Rank;
string UserName;
string UserScore;
}
So lets assume you want to load top 100 users in leaderboard and you want to have their rank included:
[OperationContract]
public List<ScoreDto> GetTop100()
{
// Linq to entities query
var query = (from u from context.Users
order by u.Score
select new
{
u.Name,
u.Score
}).Take(100);
// Linq to objects query from working on 100 records loaded from DB
// Select with index doesn't work in linq to entities
var data = query.AsEnumerable().Select((x, i) => new ScoreDto
{
Rank = i + 1,
Name = x.Name,
Score = x.Score
}).ToList();
return data;
}
what will the row number be used for? if this is for ordering might I suggest adding a column named Order, then map the column to your entity.
if you require a row index, you could also call the .ToList() on the query and fetch the index locations for each entity.
Edit:
you could add the Rank property and set it to Ignore. This will enable you to go through the collection set the rank with a simple for loop. This will also not be persisted in the database. It will also not have any required columns in the database.
It does add an extra iteration.
the other way to go about it. This would be to add the rank number in the generated UI and not in the data collection being used to bind.
A Project can have many Parts. A property on Part is Ipn, which is a string of digits.
Project "A" has Parts "1", "2", "3"
Project "B" has Parts "2", "3", "4"
Project "C" has Parts "2"
Project "D" has Parts "3"
I want to find all Projects that have all of the specified parts associated with it. My current query is
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));
This gives me back all Projects that have any of the parts, thus the result set is Projects A, B, C, and D.
The SQL where clause generated, looks something like
WHERE part1_.Id in (SELECT this_0_.Id as y0_
FROM Parts this_0_
WHERE this_0_.Ipn in ('2' /* #p0 */,'3' /* #p1 */))
My desired result would only be Projects A and B. How can I construct the NHibernate criteria to get the result set that I need?
The number of parts I search on can vary, it can be n number of parts.
yesterday I was working on the similar problem.
I had to select/load all parent-objects with exactly the given list of child-objects.
I could solve this with the Criteria-API, with only one drawback (see *1 below).
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
...
}
Here comes the Criteria:
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));
}
Back to your example: The SQL would look like this.
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 */ )
Basicaly I add for each child a subquery that checks for it's existance in the project and combine them with and, so only project with all that children will be selected.
Greetings
Juy Juka
Additional Uses
I wasn't finished with my code after this and if somone needs what I had to find out, I'll add it here. I hope the additional information belongs here, but I am not sure because it's my first post on stackoverflow.com
For the following examples we need a more complex part-class:
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;}
...
}
Different criterion on child-objects
It is possible to use the same code when you do not have the primarykey(s) of the searched parts, but would like to find the parts with other properties.
// 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));
}
Expected 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 */ )
Excluding children
To negate this and search partens who do not have the searched children is easily done by using Subqueries.PropertyNotIn instead of Subqueries.PropertyIn.
Exactly/only the searched children
This was the tricky part I had to work on the longest time. I wanted parents with exactly the given list of parts.
To stay with the building-project example: I am searching projects with a house-part and a guarden-part but no other parts
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));
Expected 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 */ )
)
(More then one house and/or one guarden is possible, since no checkon "duplicated" entries is done)
Your query requires that we make two joins from Project to Part. This is not possible in Criteria.
HQL
You can express this query directly in 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
With the Criteria API, you would query for those Projects that have one of the specified Parts, and the filter the results in C#.
Either have the criteria eager load Project.Parts, or map that as lazy="extra".
Then, using your existing criteria query from above.
// 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