Query many-to-many without selecting all objects using Criteria API - nhibernate

I have a many-to-many relationship between Project and Site. I am trying to retrieve a list of Sites for a project using the Criteria API. I've got this working but the query also selects all of the columns for the associated Projects, which I don't want. I wrote what I thought was an equivalent query using HQL and it only selects the Site columns.
var target1 = session.CreateQuery("select s from Site s join s.Projects pr where pr.ProjectId = ?")
.SetInt32(0, projectId)
.List<Site>();
var target2 = session.CreateCriteria<Site>()
.CreateAlias("Projects", "pr")
.Add(Restrictions.Eq("pr.ProjectId", projectId))
.List<Site>();
How can I limit the Criteria API version (target2) to select only the Site columns? I tried using Projections but there's no method to project a type. I have to use Criteria API in this case.

I'm not sure if this is the best way, but I got it to work using SqlProjection:
Session.CreateCriteria<T>("foo")
.CreateAlias("foo.Bar", "bar")
.SetProjection(Projections.SqlProjection("{alias}.*", new string[] {}, new IType[] {}))
.SetResultTransformer(new AliasToBeanResultTransformer(typeof(Foo)))
.List<Foo>();
This produces the following SQL:
SELECT this_.* FROM Foo this_ inner join Bar b1_ on this_.BarId=b1_.Id

Related

Direct implementation of EF Core many-to-many relationship unable to detect the related table

I've been following this tutorial on setting up a proper many-to-many relationship with EF Core. I've implemented what the author calls a direct relationship. Everything's been working fine until this example:
var books = context.Books.Tags.Select(t => t.TagId).ToList()
for which VS returns an error saying that DbSet does not contain a definition for Tags. I don't understand what's causing this, as the database is implemented just fine, and querying it with MSSQL works as expected. For example, I was able to do:
SELECT c.*
FROM Books o
JOIN BookTags ot ON ot.BooksBookId = o.TagId
JOIN Tags c ON ot.TagsTagId = c.TagId
WHERE o.BookId = 1
and get a list of Tags on Book with ID = 1. Ideally, I'd like to get the same result with EF Core, but I really don't understand what's broken here. Any help is greatly appreciated.
You cannot load navigation properties like that. To load books with navigation property, use:
var books = context.Books.Include(b => b.Tags).ToList();
To get the ids only, you can use:
var tagIds = context.Books.SelectMany(b => b.Tags).Select(t => t.TagId).ToList();

Multiple Fetches in linq to nhibernate

I was looking at this
Be careful not to eagerly fetch
multiple collection properties at the
same time. Although this statement
will work fine:
var employees =
session.Query()
.Fetch(e => e.Subordinates)
.Fetch(e => e.Orders).ToList();
I need to fetch 2 references so I would need to do something like that. Is there a better way of doing this.
I can't do .ThenFetchMany() as it goes to the into the child objects but the ones I am after on the same level.
Well, the query will still return the results you want, but as stated, it will return a cartesian product, i.e. the SQL query will return count(e.Subordinates) * count(e.Orders) results, which could add up pretty quickly, especially if you have more than just two collections.
NHibernate introduced Futures with the 2.1 release. Unfortunately there seems to be no way in the current NHibernate 3.0 release to make them work with NHibernate.Linq (session.Query<T>()).
Futures allow you to perform multiple queries in one roundtrip to the database (as long as the DB supports it, but most do). In that case you will only have count(e.Subordinates) + count(e.Orders) results, which is obviously the minimum.
Futures work with the criteria API, HQL and they are supposed to work with the new QueryOver API (I have not tested that, yet).
NHibernate.Linq does have Query().ToFuture() and Query().ToFutureValue(), but so far I only get Exceptions when I use them.
Edit:
I just checked again for the Linq API and it seems as if it is working if you do not use Fetch. The following will result in three SQL queries that are executed in one roundtrip. The total number of rows return will be 1 + count(Subordinates) + count(Orders).
int id = 1;
// get the Employee with the id defined above
var employee = repo.Session.Query<Employee>()
.Where(o => o.Id == id)
.ToFuture<Employee>();
// get the Subordinates (these are other employees?)
var subordinates = repo.Session.Query<Employee>()
.Where(o => o.HeadEmployee.Id == id)
.ToFuture<Employee>();
// get the Orders for the employee
var orders = repo.Session.Query<Order>()
.Where(o => o.Employee.Id == id)
.ToFuture<Order>();
// execute all three queries in one roundtrip
var list = employee.ToList();
// get the first (and only) Employee in the list, NHibernate will have populated the Subordinates and Orders
Employee empl = list.FirstOrDefault();
Thank you for having marked this as the answer anyway.

LLBLGEN: Linq to LLBGEN don't work

I want to make custom select from the database table using Linq. We use LLBGEN as ORM solution.
I can't do LINQ query to Entities Collection Class unless I call GetMulti(null) method of it.
Is it possible to do LINQ query to LLBGEN without extracting all table first?
BatchCollection batches = new BatchCollection();
BatchEntity batch = batches.AsQueryable()
.Where(i => i.RegisterID == 3)
.FirstOrDefault(); // Exception: Sequence don't contains any elements
batches = new BatchCollection();
batches.GetMulti(null); // I don't want to extract the whole table.
BatchEntity batch = batches.AsQueryable()
.Where(i => i.RegisterID == 3)
.FirstOrDefault(); //Works fine
To query your database using LINQ (which is different from operating on an enumerable collection using the LINQ syntax) you have to use the LinqMetaData provider that comes with LLBLGen in the yourrootnamespace.Linq assembly. Once you add this assembly to your project you can use something like this to create your db query: (from the LLBL documentation)
LinqMetaData metaData = new LinqMetaData();
var q = from c in metaData.Customer
where c.Country=="USA"
select c;
In your example above you are using LINQ syntax to perform a where clause on a collection, but this does not have anything to do with LLBL or executing a query on the database. That is why it will not work with the empty collection, but does work on the filled collection.
Look more into LinqMetaData for the specifics of querying your db using LINQ to LLBLGen.

Unique results from joined queries with NHibernate LINQ provider

I'm using NHibernate 2.1 with the LINQ provider and the results I get back from this query have multiple root nodes:
public IList<Country> GetAllCountries()
{
List<Country> results = (from country in _session.Linq<Country>()
from states in country.StateProvinces
orderby country.DisplayOrder, states.Description
select country)
.Distinct()
.ToList();
return results;
}
I know that using the Criteria API you can call DistinctRootEntityResultTransformer() to ensure that you get a unique root node, but I'm in the process of switching most of my queries over to the NHibernate LINQ provider, and I don't see an equavalient.
http://nhforge.org/wikis/howtonh/get-unique-results-from-joined-queries.aspx
Using the NorthWind database, I wanted to get back the distinct regions from the territories... This syntax worked correctly.
(from t in Territories
from r in Regions
select new
{
r.RegionDescription
})
.Distinct().OrderBy(r => r.RegionDescription)
There is a post on a Microsoft forum here that may help.

Eager loading child collection with NHibernate

I want to load root entities and eager load all it's child collection and aggregate members.
Have been trying to use the SetFetchMode in FluentNHibernate, but I am getting duplicates in one of the child collection since I have a depth of 3 levels. DistinctRootEntityResultTransformer unfortunately only removes the root duplications.
return Session.CreateInvoiceBaseCriteria(query, archived)
.AddOrder(new Order(query.Order, query.OrderType == OrderType.ASC))
.SetFetchMode("States", FetchMode.Eager)
.SetFetchMode("Attestations", FetchMode.Eager)
.SetFetchMode("AttestationRequests", FetchMode.Eager)
.SetFetchMode("AttestationRequests.Reminders", FetchMode.Eager)
.SetResultTransformer(new DistinctRootEntityResultTransformer())
.List<Invoice>();
Could I use multi queries or something similar to archive this?
Furthermore, wouldn't this approach result in unnecessarily huge result sets from the database?
Any suggestions?
Found a solution but it isn't pretty. First I go and find all the Invoice IDs, then I use them in the multiquery and then at the end filtering the results through a HashedSet. Because of the large number of items sometimes i couldn't use the normalt Restriction.In and was forced to send it as a string.
Any suggested tweaks?
var criteria = Session.CreateInvoiceBaseCriteria(query, archived)
.SetProjection(Projections.Id());
var invoiceIds = criteria.List<int>();
if (invoiceIds.Count > 0)
{
var joinedIds = JoinIDs(criteria.List<int>()); // To many ids to send them as parameters.
var sql1 = string.Format("from Invoice i inner join fetch i.States where i.InvoiceID in ({0}) order by i.{1} {2}", joinedIds, query.Order, query.OrderType.ToString());
var sql2 = string.Format("from Invoice i inner join fetch i.AttestationRequests where i.InvoiceID in ({0})", joinedIds);
var sql3 = string.Format("from Invoice i inner join fetch i.Attestations where i.InvoiceID in ({0})", joinedIds);
var invoiceQuery = Session.CreateMultiQuery()
.Add(sql1)
.Add(sql2)
.Add(sql3);
var result = invoiceQuery.List()[0];
return new UniqueFilter<Invoice>((ICollection)result);
}
return new List<Invoice>();
To answer your question: yes, it results in huge resultsets.
I suggest to:
just naively write your queries without eager fetching
On certain places, put an eager fetch, but only one per query
if you really get performance problems which you can't solve with indexes or by enhance queries and mapping strategies, use your solution with the multiple queries.
While it might not be exactly what you are looking for, I would recommend looking at this article:
Eager loading aggregate with many child collections
If you look around the rest of that site you will find even more posts that discuss eager loading and other great nHibernate stuff.