fetch required property using Criteria API? - nhibernate

I have customer and customerAddress class. CustomerAddress class have country n state object.When I load customer i want CustomerAddress also and in customerAddress i want to load only contryName and countryID other details must be null.
In short SQL Query which i want to genrate by Criteria APT is
SELECT Customer.Name, Country.CountryName,
Country.CountryID AS CountryID,
CustomerAddress.LocalAddressLine1
FROM Customer INNER JOIN CustomerAddress
ON Customer.CustomerID = CustomerAddress.CustomerID
INNER JOIN Country
ON CustomerAddress.CountryID = Country.CountryID
to achive this i did
ICriteria criteria = session.CreateCriteria(typeof(Customer), "Customer")
.CreateAlias("Customer.CustomerAddressList", "CustomerAddressList", NHibernate.SqlCommand.JoinType.LeftOuterJoin)
.CreateCriteria("CustomerAddressList.Country", "Country", NHibernate.SqlCommand.JoinType.LeftOuterJoin)
.SetProjection(Projections.ProjectionList().Add(Projections.Property("Country.CountryID"))
.Add(Projections.Property("Country.CountryName")))
.SetResultTransformer(NHibernate.Transform.Transformers.AliasToBean(typeof(Customer)))
.Add(Restrictions.Eq("Customer.EstablishmentId", CustomerId));
but this gives me Error. How can do this.
How to get specified columns using Criteria API.?
Edited As per guidence By #Firo
i moved .Add(Restrictions.Eq("Customer.EstablishmentId", CustomerId)) before SetProjection so my code is now
ICriteria criteria = session.CreateCriteria(typeof(Customer), "Customer")
.CreateAlias("Customer.CustomerAddressList", "CustomerAddressList", NHibernate.SqlCommand.JoinType.LeftOuterJoin)
.CreateCriteria("CustomerAddressList.Country", "Country", NHibernate.SqlCommand.JoinType.LeftOuterJoin)
.Add(Restrictions.Eq("Customer.EstablishmentId", CustomerId))
.SetProjection(Projections.ProjectionList().Add(Projections.Property("Country.CountryID"))
.Add(Projections.Property("Country.CountryName")))
.SetResultTransformer(NHibernate.Transform.Transformers.AliasToBean(typeof(Customer)));
customer = criteria.UniqueResult<Customer>();
This will execute successfully no error will occure but when i look for customer object all its property is null.

for AliasToBean to work correctly you need to explicitly specify the alias
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Country.CountryID"), "CountryID")
.Add(Projections.Property("Country.CountryName"), "CountryName"))
.SetResultTransformer(NHibernate.Transform.Transformers.AliasToBean(typeof(Country)));

Although you can supposedly do this using a Transformer, it may not be worth doing. For one thing it may be clearer to construct the instances yourself directly in the method. If you specify a ProjectionsList with multiple Projections on your Criteria instance then Hibernate will bring back a result list of Object[] So ...
List<Object[]> results = crit.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Country.CountryID"), "CountryID")
.Add(Projections.Property("Country.CountryName"), "CountryName")).list();
List<Customer> customers = new ArrayList<Customer>();
for ( Object[] row: results ) {
Customer c = new Customer();
c.setCountryId((Long) row[0]);
// etc. for other properties
customers.add(c);
}
See also AliasToBeanResultTransformer(MyDTO.class) fails to instantiate MyDTO

Related

How (preloading) join table on custom column?

Imagine we have the following models:
type Company struct {
gorm.Model
Name string
Addresses []Address
}
type Address struct {
gorm.Model
CompanyID uint64
Street string
City string
Country string
}
I want to take all the companies(and their addresses) which have address in a specific location. Something like this:
SELECT company.*, address.* FROM company
INNER JOIN address ON address.company_id = company.id AND address.country = 'Bulgaria'
So if a company does not have address at the specific location, I will not get it as a result at all. I was trying something like that:
db.Joins("Addresses", "addresses.country = ?", "Bulgaria").Find(&companies)
However, it doesn't work, because GORM doesn't take the second argument of Joins(when preloading join used), so I should check the generated query and make something like that:
db.Where(`"Address".country = ?`, "Bulgaria").Joins("Addresses").Find(&companies)
Is there a better way/not hacky way? Have in mind all of the above code is mock of the real problem, I didn't want to expose the original models/queries.
You can use Preload to load Addresses into the Company object.
Based on your described conditions, where you don't want to load companies that don't match your filter, you should use an INNER JOIN
Two options:
First, if your table is named company, then your query should look like this:
db.Table("company").
Preload("Addresses").
Joins("INNER JOIN addresses a ON a.company_id = company.id").
Where("a.country = ?", "Bulgaria").
Find(&companies)
Second, if your table is named companies, then you should try this:
db.Preload("Addresses").
Joins("INNER JOIN addresses a ON a.company_id = companies.id").
Where("a.country = ?", "Bulgaria").
Find(&companies)
If you are using Gorm v2 you perform CRUD operations on has-one and one-to-many via associations:
var companies []Company
var addresses []Address
countries := []string{"Bulgaria"}
db.Model(&Address).Where("country IN (?)", countries).Find(&addresses)
// The key is using the previously fetched variable as the model for the association query
db.Model(&addresses).Association("CompanyID").Find(&companies)
This also works for many-to-many relations.
When comparing Gorm VS raw SQL queries (db.Raw(SQLquery)) you will typically see a performance hit. On the upside, you will not have to deal with the added complexity of raw sql string building in Go, which is pretty nice. I personally use a combination of both raw and gorm-built queries in my projects :)

I need to get Subject name with total number of Book

I have a SQL Query which I want to convert in Linq or want to show data
as pictured here
Here is the query:
select sae_subcategorymaster.subject, count(sae_tblbookdetail.title)
from sae_tblbookdetail inner join sae_subcategorymaster
on sae_subcategorymaster.subject=sae_tblbookdetail.subject
group by sae_subcategorymaster.subject
What is a simple way to do this?
Grouping is supported in LinqEF. Provided you have your entities related. (Books have a reference to their subcategory.)
var totals = context.Books
.GroupBy(book => book.SubCategory.Subject)
.Select(group => new
{
Subject = group.Key,
BookCount = group.Count()
}).ToList();

NHibernate: Why does Linq First() force only one item in all child and grandchild collections with FetchMany()

Domain Model
I've got a canonical Domain of a Customer with many Orders, with each Order having many OrderItems:
Customer
public class Customer
{
public Customer()
{
Orders = new HashSet<Order>();
}
public virtual int Id {get;set;}
public virtual ICollection<Order> Orders {get;set;}
}
Order
public class Order
{
public Order()
{
Items = new HashSet<OrderItem>();
}
public virtual int Id {get;set;}
public virtual Customer Customer {get;set;}
}
OrderItems
public class OrderItem
{
public virtual int Id {get;set;}
public virtual Order Order {get;set;}
}
Problem
Whether mapped with FluentNHibernate or hbm files, I run two separate queries, that are identical in their Fetch() syntax, with the exception of one including the .First() extension method.
Returns expected results:
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).ToList()[0];
Returns only a single item in each collection:
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).First();
I think I understand what's going on here, which is that the .First() method is being applied to each of the preceding statements, rather than just to the initial .Where() clause. This seems incorrect behavior to me, given the fact that First() is returning a Customer.
Edit 2011-06-17
After further research and thinking, I believe that depending on my mapping, there are two outcomes to this Method Chain:
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items);
NOTE: I don't think I can get subselect behavior, since I'm not using HQL.
When the mapping is fetch="join" I should get a cartesian product between the Customer, Order and OrderItem tables.
When the mapping is fetch="select" I should get a query for Customer, and then multiple queries each for Orders and OrderItems.
How this plays out with adding the First() method to the chain is where I lose track of what should be happening.
The SQL Query that get's issued is the traditional left-outer-join query, with select top (#p0) in front.
The First() method is translated into SQL (T-SQL at least) as SELECT TOP 1 .... Combined with your join fetching, this will return a single row, containing one customer, one order for that customer and one item for the order. You might consider this a bug in Linq2NHibernate, but as join fetching is rare (and I think you're actually hurting your performance pulling the same Customer and Order field values across the network as part of the row for each Item) I doubt the team will fix it.
What you want is a single Customer, then all Orders for that customer and all Items for all those Orders. That happens by letting NHibernate run SQL that will pull one full Customer record (which will be a row for each Order Line) and construct the Customer object graph. Turning the Enumerable into a List and then getting the first element works, but the following will be slightly faster:
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items)
.AsEnumerable().First();
the AsEnumerable() function forces evaluation of the IQueryable created by Query and modified with the other methods, spitting out an in-memory Enumerable, without slurping it into a concrete List (NHibernate can, if it wishes, simply pull enough info out of the DataReader to create one full top-level instance). Now, the First() method is no longer applied to the IQueryable to be translated to SQL, but it is instead applied to an in-memory Enumerable of the object graphs, which after NHibernate has done its thing, and given your Where clause, should be zero or one Customer record with a hydrated Orders collection.
Like I said, I think you're hurting yourself using join fetching. Each row contains the data for the Customer and the data for the Order, joined to each distinct Line. That is a LOT of redundant data, which I think will cost you more than even an N+1 query strategy.
The best way I can think of to handle this is one query per object to retrieve that object's children. It would look like this:
var session = this.generator.Session;
var customer = session.Query<Customer>()
.Where(c => c.CustomerID == id).First();
customer.Orders = session.Query<Order>().Where(o=>o.CustomerID = id).ToList();
foreach(var order in customer.Orders)
order.Items = session.Query<Item>().Where(i=>i.OrderID = order.OrderID).ToList();
This requires a query for each Order, plus two at the Customer level, and will return no duplicate data. This will perform far better than a single query returning a row containing every field of the Customer and Order along with each Item, and also better than sending a query per Item plus a query per Order plus a query for the Customer.
I'd like to update the answer with my found so that could help anybody else with the same problem.
Since you are querying the entity base on their ID, you can use .Single instead of .First or .AsEnumerable().First():
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).Single();
This will generate a normal SQL query with where clause and without the TOP 1.
In other situation, if the result has more than one Customer, exception will be thrown so it won't help if you really need the first item of a series based on condition. You have to use 2 queries, one for the first Customer and let the lazy load do the second one.

NHibernate: returning List<EntityType> not List<Object[]> when using grouping

when using Nhiberante Criteria API or HQL with grouping, query returns list of arrays of entity properties List<Object[]> at which grouping was made. If I need to return only certain property how can I do that? preferably with Nhiberane API if possible
Have you tried using the Transformers class?
See section 16.1.5
With HQL, you just SELECT the properties you want:
var query = Session.CreateQuery("select p.Id, p.Price from Products p where p.Status = 'A'")
.List().Cast<object[]>();
It's similar with NHibernate.Linq:
var query = from p in Session.Linq<Product>()
where p.Status == "A"
select new
{
p.Id, p.Price
};

NHibernate Criteria Transform Result

I have an SecurityGroup entity witch has Memebers and Application properties.
Application is a lookup.
So securityGroups is in many-to-many relationship with User table and one-to-many with LookupApplciation (FK)
Now I want to select all application linked to a specific user.
I have follow criteria:
public IList<LookupApplication> GetApplicationByUser(User user)
{
return
this.Session.CreateCriteria(typeof(SecurityGroup), "sg")
.CreateAlias("Members", "u")
.CreateAlias("Application", "al")
.Add(Restrictions.Eq("u.Id", user.Id))
.List<LookupApplication>();
}
It trows an exception
The value "Edi.Advance.Core.Model.Security.SecurityGroup" is not of type "Edi.Advance.Core.Model.Lookups.LookupApplication" and cannot be used in this generic collection.
Parameter name: value
and it is right.
How can I transform the result to IList<LookupApplication>?
Thanks
You can only return the type which you create the criteria from.
The easiest way starting from the code you have will be:
return
this.Session.CreateCriteria(typeof(SecurityGroup), "sg")
.CreateAlias("Members", "u")
.CreateAlias("Application", "al")
.Add(Restrictions.Eq("u.Id", user.Id))
.List<SecurityGroup>()
// get the lookup applications in memory
.SelectMany(x => x.LookupApplications);
This loads all SecurityGroups into memory, even if it only needs the LookupApplications. This might not be an issue when you need them anyway or when they are small.
You could also reverse the query and start from the LookupApplication
return
this.Session.CreateCriteria(typeof(LookupApplication), "la")
// requires navigation path from SecurityGroup to LookupApplication
.CreateCriteria("la.SecurityGroup", "sg")
.CreateAlias("Members", "u")
.CreateAlias("Application", "al")
.Add(Restrictions.Eq("u.Id", user.Id))
.List<LookupApplication>()
Or use HQL, which has some features not available in Criteria, items gets all the items from a collection:
select sg.LookupApplications.items
from SecurityGroup sg inner join sg.Members u
where u.Id = :userId
HQL is actually recommended when you don't have dynamic queries.
Update, from isuruceanu's comment:
Session
.CreateQuery(
#"select sg.Application
from SecurityGroup sg
inner join sg.Members u
where u.Id = :userId")
.SetParameter("userId", user.Id)
.List<LookupApplication>();
It depends on how the SecurityGroup looks like and how LookupApplication looks like.
You could use ResultTransformer like:
.SetResultTransformer(Transformers.AliasToBean<LookupApplication>())
.List<LookupApplication>();
Granted that SecurityGroup has properties matchinig LookupAppliaction, or else you have to project those properties like:
.SetProjection(NHibernate.Criterion.Projections.ProjectionList()
.Add(Projections.Property("Number"), "OrderNumber")
.Add(Projections.Property("CreatedOn"), "CreatedOn")
.Add(Projections.Property("MemeberName"), "Name"))
.SetResultTransformer(Transformers.AliasToBean<LookupApplication>())
.List<LookupApplication>();