I have to join a table TransactionDeclaration(Id,...) with a parametric function fp_Transaction_ACL(userId) which returns (TransactionDeclarationId,AccessRightId). The join must be an inner join.
I have done the following mapping using Fluent NHibernate:
public TransactionDeclarationMap()
{
this.Id(transactionDeclaration => transactionDeclaration.Id);
this.Join(
"fp_TransactionDeclaration_ACL(:AclFilter.userId)",
join =>
{
join.KeyColumn("TransactionDeclarationId");
join.Map(transactionDeclaration => transactionDeclaration.AccessRight, "AccessRightType").CustomType
<AccessRight>().Generated.Always().ReadOnly();
join.Inverse();
join.Fetch.Join();
});
}
Since the function must not be inserted or updated, I have added the 'join.Inverse()' as explained here :
Nhibernate/hibernate Avoid Insert in joined table or view
But when I add this inverse, the join becomes a left outer join which is not ok for my use case. I need an inner join to filter records of the table 'TransactionDeclaration' not returned by the function.
How can I get an inner join ? Or is there an alternative to the 'Inverse' to avoid inserting in the function ?
I don't know how to do it in Fluent, but you'll have to specify somewhere that the key where you're joining on, is not nullable.
Something like this, perhaps:
public TransactionDeclarationMap()
{
this.Id(transactionDeclaration => transactionDeclaration.Id);
this.Join(
"fp_TransactionDeclaration_ACL(:AclFilter.userId)",
join =>
{
join.KeyColumn("TransactionDeclarationId");
join.Map(transactionDeclaration => transactionDeclaration.AccessRight, "AccessRightType").CustomType
<AccessRight>().Generated.Always().ReadOnly();
join.Inverse();
join.SetAttribute ("optional", false);
join.Fetch.Join();
});
}
click for more info.
Related
I have an association of Price belongsTo Season
I am trying to query all prices that match a specific date range when passed in the season as well as any that have none (Prices.season_id=0)
Here is what I have:
// build the query
$query = $this->Prices->find()
->where(['product_id'=>$q['product_id']])
->contain(['Seasons']);
if(!empty($to_date) && !empty($from_date)) {
$query->matching('Seasons', function ($q) {
return $q->where([
'from_date <= ' => $to_date,
'to_date >= ' => $from_date
]);
});
}
However, this will only return Prices explicitly associated with a Season. How do I make it return Prices.season_id=0 also?
The $query->matching() call internally creates a INNER JOIN and places the where-statements of the callback-function into the ON clause of the join. For retrieving items without the association you need a LEFT JOIN. So your codesnippet would look like this:
if(!empty($to_date) && !empty($from_date)) {
$query->leftJoinWith('Seasons', function ($q){return $q;});
$query->where([[
'or' => [
'Season.id IS NULL',
[
'from_date <= ' => $to_date,
'to_date >= ' => $from_date,
],
],
]]);
}
So we create a normal INNER JOIN and place the conditions in the normal (outmost) where clause of the query.
The double array is for disambiguation of probably other where conditions with an or connection.
I myself stumbled over the column IS NULL instead of 'column' => null syntax.
PS: This works for all associations. For hasMany and belongsToMany you have to group the results with $query->group('Prices.id')
I have class Place which may or may not have one User.
// I have nothing on User related to Place
public PlaceMap()
{
Id( x=> x.Id, "id").GeneratedBy.Identity();
References(x => x.UserManager, "user_manager_id").Nullable().Cascade.All();
}
When querying over Place, I want always to left join since it is Nullable.
The problem is that the generated sql has inner join.
The query:
var queryList = _dalSession.CreateCriteria<T>();
queryList.CreateAlias("UserManager", "UserManager");
You can explicitly set the join type when calling CreateAlias:
using NHibernate.SqlCommand;
// ...
var queryList = _dalSession.CreateCriteria<T>();
queryList.CreateAlias("UserManager", "UserManager", JoinType.LeftOuterJoin);
If you want to make this behavior the default, you can do so via the mapping.
In mapping by configuration file, specify `fetch="join"
With FluentNH, specify .Fetch.Join()
Using NHibernate mapping-by-code:
classMapper.ManyToOne(
x => x.UserManager,
manyToOneMapper =>
{
manyToOneMapper.Column("user_manager_id");
manyToOneMapper.NotNullable(false);
manyToOneMapper.Lazy(LazyRelation.NoLazy);
manyToOneMapper.Fetch(FetchKind.Join);
}
)
I got this query
var pc = _session.Query<ValutaHistory>()
.Where(x => x.Valutum.ValutaBetegn == updateLine.ProductCurrency)
.Fetch(x => x.Valutum)
.OrderByDescending(x => x.ValutaHistoryID)
.First();
But it results to this SQL:
select TOP (1) valutahist0_.ValutaHistoryID as ValutaHi1_187_0_,
valutum1_.ValutaID as ValutaID191_1_,
valutahist0_.Kurs as Kurs187_0_,
valutahist0_.ts as ts187_0_,
valutahist0_.cts as cts187_0_,
valutahist0_.nts as nts187_0_,
valutahist0_.KjopKurs as KjopKurs187_0_,
valutahist0_.ValutaID as ValutaID187_0_,
valutum1_.ValutaBetegn as ValutaBe2_191_1_,
valutum1_.KursDato as KursDato191_1_,
valutum1_.Kurs as Kurs191_1_,
valutum1_.Enhet as Enhet191_1_,
valutum1_.Myntsort as Myntsort191_1_,
valutum1_.BrukesSalg as BrukesSalg191_1_,
valutum1_.Aktiv as Aktiv191_1_,
valutum1_.ts as ts191_1_,
valutum1_.cts as cts191_1_,
valutum1_.nts as nts191_1_,
valutum1_.TallKode as TallKode191_1_,
valutum1_.Symbol as Symbol191_1_,
valutum1_.TallKode1 as TallKode14_191_1_,
valutum1_.TallKode2 as TallKode15_191_1_,
valutum1_.KjopKurs as KjopKurs191_1_,
valutum1_.CultureName as Culture17_191_1_,
valutum1_.TallKode3 as TallKode18_191_1_,
valutum1_.ValutaTabellID as ValutaT19_191_1_
from ValutaHistory valutahist0_
left outer join Valuta valutum1_
on valutahist0_.ValutaID = valutum1_.ValutaID
order by valutahist0_.ValutaHistoryID desc
Obviously WHERE clause is just missing, how is this possible?
Well, when you put a condition on the left side of a left join (your where clause), you essentially nullify the left join (for related tables).
The left join is there to return records from the right side even if there is no match on the left. However, when you put a condition on the left table, you effectively have an inner join.
I'm not sure, but I suspect that NHibernate is detecting this, and deciding that your OrderBy() and First() clauses take precedence over the Where() clause.
So I would turn this query around. Query on, and filter on, the parent entity Valutum, then Fetch the child ValutaHistory and sort.
var pc = _session.Query<Valutum>()
.Where(x => x.ValutaBetegn == updateLine.ProductCurrency)
.FetchMany(x => x.ValutaHistory)
.OrderByDescending(x => x.ValutaHistoryID)
.First();
This works
var pc = _session.Query<ValutaHistory>()
.Where(x => x.Valutum.ValutaBetegn == updateLine.ProductCurrency)
.OrderByDescending(x => x.ValutaHistoryID)
.Fetch(x => x.Valutum)
.First();
The idea is to get all groups and fetch members. I have problems fetching only active users. Can you help me?
Mapping:
public class GroupDtoMap : DtoClassMap<GroupDto>
{
public GroupDtoMap()
{
Id(g => g.Id, "group_id");
HasManyToMany(g => g.Members) // Members is a List<UserDto>
.Table("members")
.ParentKeyColumn("group_id")
.ChildKeyColumn("user_id");
}
}
Query:
var groups = Session
.CreateCriteria<GroupDto>()
.Add(Expression.Eq("IsActive", true)) // Get only active groups
.SetFetchMode("Members", NHibernate.FetchMode.Eager)
.SetResultTransformer(new DistinctRootEntityResultTransformer())
.Future<GroupDto>();
What I tried but still failing:
HasManyToMany(g => g.Members)
.Table("members")
.ParentKeyColumn("group_id")
.ChildKeyColumn("user_id")
.ChildWhere("status = 1");// the column in DB is 'status' with values 0/1
EDIT:
OK. It looks like the ChildWhere() is actually working. It was a nasty cache. The problem now is how to force inner join fetching the collection. At the moment it is doing left outer join. Any ideas?
As far as I know you cant. Sorry.But you can allways try to play with the mappings
I have Entity 'Content'. Each Content has a 'Placement' property. Placement has a many-to-many relationship width 'AdType' entity (Placement has IList<\AdType> property mapped).
I need to load all Placements that are used at least in one Content and associated width specified AdType.
My DAL function looks like this:
public IList<Placement> Load(AdType adType)
{
return NHibernateSession.QueryOver<Content>()
.JoinQueryOver(content => content.Placement)
.JoinQueryOver<AdType>(placement => placement.AdTypes)
.Where(_adType => _adType.Id == adType.Id)
.Select(x => x.Placement).List<Placement>();
}
This works fine but when I look to the SQL log i see:
SELECT this_.PlacementId as y0_ FROM AdManager.dbo.[Content] this_ inner join AdManager.dbo.[Placement] placement1_ on this_.PlacementId=placement1_.PlacementId inner join AdManager.dbo.AdTypeToPlacement adtypes5_ on placement1_.PlacementId=adtypes5_.PlacementId inner join AdManager.dbo.[AdType] adtype2_ on adtypes5_.AdTypeId=adtype2_.AdTypeId WHERE adtype2_.AdTypeId = #p0
SELECT placement0_.PlacementId as Placemen1_26_0_, placement0_.Name as Name26_0_ FROM AdManager.dbo.[Placement] placement0_ WHERE placement0_.PlacementId=#p0
SELECT placement0_.PlacementId as Placemen1_26_0_, placement0_.Name as Name26_0_ FROM AdManager.dbo.[Placement] placement0_ WHERE placement0_.PlacementId=#p0
This means that NHibernate takes all placements Id in first query and then queries all fields from Placement table by Id.
My question is: Does enyone know how to modify QueryOver method to force NHibernate load data in one query?
it seems NHibernate does think there might be something in the where which maybe filters out data which is needed tro initialize the placement. You can go with a subquery:
public IList<Placement> Load(AdType adType)
{
var subquery = QueryOver.For<Content>()
.JoinQueryOver(content => content.Placement)
.JoinQueryOver<AdType>(placement => placement.AdTypes)
.Where(_adType => _adType.Id == adType.Id)
.Select(x => x.Id);
return NHibernateSession.QueryOver<Content>()
.WithSubquery.Where(content => content.Id).IsIn(subquery))
//.Fetch(x => x.Placement).Eager try with and without
.Select(x => x.Placement).List<Placement>();
}
or SQL (has the disadvantage that it just fills the new Placement but doest track it)
public IList<Placement> Load(AdType adType)
{
return NHibernateSession.CreateSQLQuery("SELECT p.Name as Name, ... FROM content c join placement p...")
.SetResultTransformer(Transformers.AliastoBean<Placement>())
.List<Placement>();
}