Sub-optimal queries over many-to-many relations with HQL - nhibernate

I have two entities, Location and Industry, and a link-table between them. I've configured a many-to-many relationship, in both directions, between the two entities.
In a search query, I'm trying to select Locations that are associated with a list of industries.
After days and days of trying to wrangle the criteria API, I've decided to drop down to HQL and abandon the criteria API. But even that isn't going well for me - it seems, regardless of whether I hand-write this HQL query, or let the criteria API do it, I end up with the same result.
I managed to produce the right result in two ways - like this:
var q = Data.Query("select distinct loc from Location loc join loc.Industries ind where ind in (:ind)");
q.SetParameterList("ind", new Industry[] { Data.GetIndustry(4), Data.GetIndustry(5) });
And (better) like that:
var q = Data.Query("select distinct loc from Location loc join loc.Industries ind where ind.id in (:ind)");
q.SetParameterList("ind", new int[] { 4, 5 });
Unfortunately, both result in a sub-optimal query:
select distinct
location0_.Id as Id16_,
location0_.Name as Name16_,
(etc.)
from Location location0_
inner join LocationIndustry industries1_
on location0_.Id=industries1_.LocationId
inner join Industry industry2_
on industries1_.IndustryId=industry2_.Id
where
industry2_.Id in (? , ?)
Why the extra join?
Is NH not smart enough to know that the Industry.Id property, being the only Industry-property involved in the query, is stored in the LocationIndustry link-table, and there is no need for the extra join to the Industry table itself?
Or am I doing something wrong?
Ideally, the most intuitive thing for me would be to write:
from Location loc where loc.Industries in (:ind)
This does not work - it throws an error and says it does not know about the Industries property. I guess because Industries, being a "property" in programming terms, is actually a "relationship" in terms of DBMS.
What is the simplest and most efficient way to write this query in HQL?
Thanks!

I'm not sure you can avoid this extra join given the mapping strategy you have used.
You could avoid it by using an intermediary class but this would mean you would need a class structure like this:
public class Industry {
//Other stuff
public virtual List<LocationIndustry> LocationIndustries {get; set:;}
}
public class LocationIndustry {
public virtual Location Location {get; set;}
public virtual Industry Industry {get; set;}
}
public class Location {
//normal stuff
public virtual IList<LocationIndustry> LocationIndustries {get; set;}
}
Then you can query on the LocationIndustry class and avoid the join to Location.

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 :)

Simple Linq-to-entities query involving .Include I believe

I have a Linq-to-Entities query that is not complicated but requires an .include and/or projection and/or join because it must be executed in one pass.
Here is my database (Microsoft SQL Server 2008):
Table A (Customers) (contains CustomerID (customer IDs), and ZipCode (zip codes) as strings.
Table C (Categories) (contains CategoryID (categories) like "food", "shelter","clothing", "housing" (primary keys).
Table A_C is a linking table, since Tables A and C are linked as many-to-many: contains just two fields: CustomerID "customer IDs" and CategoryID (Categories), in combination as primary keys. This table is a linking table betweeen tables A and C.
Here is my query, that must be executed in just one trip to the database: I need to select all records in Table A that satisfy a condition, then filter these records depending on a 'list of parameters' that are found in the linking Table A_C--and do this all in one trip to the database. But I don't know what the length or composition of the list of parameters for Table A_C is, ahead of time--it varies from call to call. Thus this list of parameters varies method call by method call.
To give a more concrete example:
Table A has a list of customer IDs. I find the customers that live in a certain Zip code. Then, in the same SQL query, I need to find which of these customers have selected certain categories: Food, Clothing, Housing, etc, but my web method does not know ahead of time what these categories are, rather, they are passed as a list to the method: List myCategoryList (which could be 1 category or 100 categories, and varies method call by method call).
How do I write the projection using Linq-to-Entities? When the list of parameters varies? And do it all in one pass?
List<string> CategoryList = new List<string>() { "Food", "Shelter", "Housing" }; // in one call to the web service method
List<string> CategoryList = new List<string>() { "Food", "Clothing" }; //could be a second call--it varies and I don't know ahead of time what the List will be
So how can I do the SQL query using Linq-to-Entities? In one pass? (Of course I could loop through the list, and make repeated trips to the database, but that's not an optimal solution I am told). Projection,.Include are keywords but surfing the net yielded nothing.
Here is a crude guess, just to get ball rolling:
public void WebMethod1 (CategoryList)
{
using (EntityFramework1 context = new EntityFramework1())
{
/* assume CategoryList is a list of strings passed into the method and is,for this particular call,something like: List<string> CategoryList = new List<string>() { "Food", "Clothing" }; for this call, but in the next call it could be: List<string> CategoryList = new List<string>() { "Food", "Shelter", "Housing" } */
string ZipCodeString = "12345";
string customerIDString = "E12RJ55";
var CustomersFromZipCodeHavingSelectedCertainCategories = from x in context.A_C
where x.A.CustomerID == customerIDString
where x.A.StartsWith(ZipCodeString)
where x.A_C.Contains(CategoryList) //???? This is clearly not grammatical, but what is?
select x;
}
/*
my problem is: I want to filter all records from A that contain a zipcode 12345, and that also have a certain CustomerID "E12RJ55" from table A, but further filter this set with all such CustomerIDs in linking table A_C that contain the categories "Food" and "Clothing".
How to do this in one pass? I can do this quite easily in multiple passes and trips to the database using code, but somebody in this thread here http://bit.ly/rEG2AM suggested I do a Join/projection and do it all in one fell swoop.
*/
I will also accept SQL answers since it might help yield a solution. This question btw is not difficult I believe--but I could not find an answer on the net.
EDIT: with answer and credit to david s.
I thank you for the answer david.s. Here is what worked, slightly different than the answer by david.s, in that I am using the linking table (bridge table) called “Customer_Categories” that is between the table Customer and Categories and contains the primary key of each (as is required for many-to-many relationships). This bridge table is what I called "A_C" in my original answer, and here has ints rather than strings but is the same thing. Intellisense picked up this table and I used it, and it works. Also keep in mind that CategoryList is a list of ints, List CategoryList = new List();, yet amazingly it automagically works inside this SQL-to-Entities query:
Var CustomersFromZipCOde = context.Customers.Where (custo => custo.CustomerID==customerIDString && custo.ZipCode.StartsWith(ZipCodeString) && custo.Customer_Categories.Any(categ => CategoryList.Contains(categ.CategoryID)));
//gives the right output, incredible.
First of all i would like to say that even if you explanation is very long it is not very clear. You would like a simple Linq-to-Entities query but you don't give the Entities, you only speak of tables in your database.
Assuming you have the following entities:
public class Customer
{
public string CustomerID { get; set; }
public string ZipCode { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public string CategoryID { get; set; }
public virtual ICollection<Customer> Customers { get; set; }
}
Your query might look like this:
var CustomersFromZipCodeHavingSelectedCertainCategories =
context.Customers.Where(
customer => customer.CustomerID == customerIDString &&
customer.ZipCode.StartsWith(ZipCodeString) &&
customer.Categories.Any(
category => CategoryList.Contains(category.CategoryID));
More info on other ways to do this here:
http://smehrozalam.wordpress.com/2010/06/29/entity-framework-queries-involving-many-to-many-relationship-tables/

How to setup NHibernate criteria to perform such query?

How to setup NHibernate criteria to perform such query?
SELECT
COUNT(1) AS CNT,
l.CAMPAIGN_ID AS CAMPAIGN_ID,
MAX(mc.NAME) AS CAMPAIGN_NAME
FROM
SA_LEADS l
INNER JOIN
SA_CAMPAIGNS mc
ON
l.CAMPAIGN_ID = mc.ID
GROUP BY
CAMPAIGN_SOURCED_ID
ORDER BY
CNT DESC
I have mappings for both tables SA_LEADS and SA_CAMPAIGNS.
For me I would start with the element that I am trying to group by so I would start with a
base of campaign. I can't tell from your query but I would assume that your mappings have a way of moving from campaign to sales lead. I have assumed this is called "SALES_LEAD_LIST"
I also like to make a small class to receive the projection result so would create something to hold the result.
public class CAMPAIGN_PERFORMANCE
{
public CAMPAIGN_PERFORMANCE() {}
public int CP_ID {get; set;}
public string CP_NAME {get; set;}
public int CP_NO_OF_SALES_LEADS {get; set;}
}
Once you have something to put your projection result in you can create a standard criteria and just push it into you new class through a projection
ICriteria criteria = base.Session.CreateCriteria(typeof(SA_CAMPAIGNS));
criteria.CreateAlias("SALES_LEADS_LIST", "SA_LEADS", JoinType.InnerJoin);
criteria.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("ID"), "CP_ID")
.Add(Projections.Property("CAMPAIGN_NAME"), "CP_NAME")
.Add(Projections.CountDistinct("SA_LEADS.ID"), "CP_NO_OF_SALES_LEADS")
.Add(Projections.GroupProperty("ID"));
.Add(Projections.GroupProperty("CAMPAIGN_NAME")));
IList<CAMPAIGN_PERFORMANCE> cpProjections = criteria
.SetResultTransformer(
new AliasToBeanResultTransformer(typeof(CAMPAIGN_PERFORMANCE)))
.List<CAMPAIGN_PERFORMANCE>();

Order By Aggregate Subquery with NHibernate ICriteria or QueryOver

Is there a way to achieve SQL like this with NHibernate ICriteria or QueryOver?
select *
from [BlogPost] b
inner join (select blogpost_id, count(*) matchCount
from [Tag]
where name in ('tag X', 'tag Y')
group by blogpost_id
) tagmatch
on tagmatch.blogpost_id = b.Id
order by tagmatch.matchCount desc
The aim is to rank blog posts by the number of matching tags so that a post with both tag X and tag Y comes above posts with just tag X.
I've got this so far:
DetachedCriteria
.For<Tag>("tag")
.Add(Restrictions.In(Projections.Property<Tag>(x => x.Name), tags.ToArray()))
.SetProjection(Projections.Group<Tag>(t => t.BlogPost))
.CreateCriteria("BlogPost")
.SetFetchMode("BlogPost", FetchMode.Eager)
.AddOrder(Order.Desc(Projections.RowCount()));
However, the resulting query doesn't join fetch BlogPost. Instead it returns just the ids, which leads to select n+1 when the BlogPosts are iterated.
public class BlogPost
{
...
ISet<Tag> Tags {get; set;}
}
public class Tag
{
BlogPost BlogPost { get; set; }
string Name { get; set; }
}
This looks like a similar issue.
Is this now possible with NHibernate 3?
If not, is there an alternative solution?
I can change schema & domain model if necessary. I don't want to use SQL or HQL if possible.
I know this question was put some time ago, but I want to do about the same thing, please take a look to my question here, and this guy here, maybe you can use the idea.

How to query a subproperty with NHibernate’s criteria api and the entity to load only the subproperties matching a predicate condition

Assuming the following:
public class Order
{
public virtual int OrderId {get;set}
public virtual ISet<Product> Products {get;set}
}
public class Product
{
public virtual int ProductId {get;set}
public virtual string ProductName {get;set}
}
How would you query using the criteria api so that only an order with a specific orderid is returned and its Product collection should also be filtered down to Products whose Name start with the lettter P?
Simplest approach is to use an alias:
var productIdToSelect = 9;
var crit = Session.CreateCriteria(typeof(Order));
crit.CreateAlias("Product", "prod");
crit.Add(Expression.Eq("prod.Id", productIdToSelect));
var result = crit.List<Order>();
I would go about this with a DetachedCriteria:
DetachedCriteria crit = DetachedCriteria.For<Order>();
crit.Add(Restrictions.Eq("OrderId",orderID);
crit.CreateCriteria("Products","products");
crit.Add(Restrictions.Like("products.ProductName","P%");
crit.List();
and then executing the criteria and getting the results.
I don't know the code you would have to write, but a point in the right direction:
http://www.nhforge.org/doc/nh/en/index.html#querycriteria-associations (14.4)
The key seems to be:
.SetResultTransformer(CriteriaUtil.AliasToEntityMap)
The documentation shows an example with cats and kittens.
Note that the kittens collections held by the Cat instances returned by the previous two queries are not pre-filtered by the criteria! If you wish to retrieve just the kittens that match the criteria, you must use SetResultTransformer(CriteriaUtil.AliasToEntityMap).
Set up a filter on the mapping of the collection.
<filter name="letterFilter" condition="ProductName like ':letterSupplied'"/>
Then before running the Order query enable the filter
session.EnableFilter("letterFilter").SetParameter("letterSupplied", "P%");
then run the query
Order ord = session.CreateCriteria<Order>().Add(Restrictions.IdEq(suppliedId)).UniqueResult<Order>();
Note that the single quotes in the filter definition may not be required and also i place the % symbol with the supplied parameter as i don't know how NH would react a filter like
<filter name="letterFilter" condition="ProductName like ':letterSupplied%'"/>
or
<filter name="letterFilter" condition="ProductName like :letterSupplied%"/>