QueryOver OrderBy child property using strings - nhibernate

I'm struggling with using QueryOver.OrderBy with strings for property names on child entities. e.g. the following works but I am hardcoding the OrderBy field.
Customer custAlias = null;
session.QueryOver<Campaign>()
.JoinAlias(x => x.Customer, () => custAlias)
.OrderBy(() => custAlias.Name).Desc() // want to use string property name
.List();
I can specify the OrderBy using a string with something like:
.OrderBy(Projections.Property("DOB")).Desc();
But this is looking for "DOB" on the Campaign entity, not the child Customer entity. Is it possible to retrieve the alias used by NH and then set the path to the property e.g.
.OrderBy(Projections.Property("cust.DOB")).Desc(); // where "cust" is the alias
Any ideas?

The alias used is the name of the variable. So
Projections.Property("custAlias.DOB")
(can't test now, but if I remember corretly it works)
Interestingly, it isn't the variable, in itself, that is used as the alias, but its name. What does it means?
QueryOver<Campaign> query;
{
Customer custAlias = null;
query = session.QueryOver<Campaign>()
.JoinAlias(x => x.Customer, () => custAlias)
}
{
Customer custAlias = null;
var result = query.OrderBy(() => custAlias.Name).Desc() // want to use string property name
.List()
}
Two different custAlias, but it still works :-)
(useful if you want to split pieces of a query in multiple methods... The only important thing is that they use the same naming for the aliases)

Related

Latest modified row for each item

I have a sql table containing multiple rows with a memberid and lastmodified date. I need to get latest modified row for each member id. This is what I have tried in EFCore 3.1.1:
var a = context.Members
.Include(m => m.Histories.OrderByDescending(h => h.LastModifiedDate)
.FirstOrDefault());
and it gives error: Lambda expression used inside Include is not valid
What am I missing?
UPDATE:
I tried this as well that didn't work either:
var a = context.Histories
.GroupBy(h => h.MemberId)
.Select(g => g.OrderByDescending(p => p.LastModifiedDate).FirstOrDefault()).ToList();
The LINQ expression '(GroupByShaperExpression:
KeySelector: (o.MemberId),
ElementSelector:(EntityShaperExpression:
EntityType: History
ValueBufferExpression:
(ProjectionBindingExpression: EmptyProjectionMember)
IsNullable: False
)
)
.OrderByDescending(p => p.LastModifiedDate)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
While newer versions of EF Core do support some filtering via adding Where clauses, it's best to think of entities as a "complete" or "complete-able" representation of your data state. You either use the complete state of data, or you project to get the details you want.
For example, if you just want the last modified date for each member:
var lastModifiedDetails = context.Members
.Select(m => new
{
m.MemberId,
LastModifiedDate = m.Histories.OrderByDescending(h => h.LastModifiedDate)
.Select(h => h.LastModifiedDate)
.FirstOrDefault()
}).ToList();
This would give you a collection containing the MemberId and it's LastModifiedDate.
Alternatively, if you want the complete member and a quick reference to the last modified date:
var memberDetails = context.Members
.Select(m => new
{
Member = m,
LastModifiedHistory = m.Histories.OrderByDescending(h => h.LastModifiedDate)
.FirstOrDefault()
}).ToList();
Here instead of trying to get the last modified date or the latest history through a Member entity, you get a set of anonymous types that project down that detail. You iterate through that collection and can get the applicable history and/or modified date for the associated Member.

How to get result filtered using property of table reference by foreign key in NHibernate

I am using NHibernate to fetch data form SQL Server.
To get data I wrote var result = Repository.QueryOver<table_reference>().Where(c => c.Amount >100).List()
Now I want to get result filtered by foreign key reference something like
Repository.QueryOver<TaxInvoicePassing>().Where(c => c.Branch.Employee.Salary > 10000).List()
How can I achieve this?
One way is iterate for each table's record and then addrange of results
Thanks in advance
Using QueryOver, you can try this:
// declare the alias
Branch branch = null;
Employee employee = null;
Session.QueryOver<TaxInvoicePassing>() // get a queryOver of TaxInvoicePassing
.JoinAlias(t => t.Branch, () => branch) // add a join with Branch
.JoinAlias(() => branch.Employee, () => employee) // add a join with Employee
.Where(c => employee.Salary > 10000) // add where filter on employee's salary
.List();
Using Linq, it could be done like the expression you asked:
Session.Query<TaxInvoicePassing>()
.Where(c => c.Branch.Employee.Salary > 10000) // this expression will be converted on a join
.List();
I would move these queries inside the repository object instead exposing it.

Restrict fluent nhibernate queryover on multiple child entities comparing properties with a single value

I would like to restrict a query using the QueryOver mechanism in Fluent Nhibernat. I found that I can do this using WhereRestrictOn but there seems to be no possibility to compare to just one value. A IsEqual method of sorts.
I quick example might explain better what my issue is
class Parent{
IList<Child1> C1 {get;set;}
IList<Child2> C2 {get;set;}
}
class Child1{
int Id {get;set;}
}
class Child2{
int Id {get;set;}
}
//What I can do
var result = session.QueryOver<Parent>()
.WhereRestrictionOn(x => x.C1.Id).IsIn(new[]{3})
.AndRestrictionOn(x => x.C2.Id).IsIn(new[]{5}).List();
//What I would like to do
var result = session.QueryOver<Parent>()
.WhereRestrictionOn(x => x.C1.Id).IsEqual(3)
.AndRestrictionOn(x => x.C2.Id).IsEqual(5).List();
So basically my issue is that I'm not able to compare with one value but always have to artifically create an array. Is this not possible or am I missing something?
If it is possible please tell me how. If it is not possible I would appreciate an explantion as to why not.
Thanks in advance.
Try this:
Child1 c1Alias = null;
Child2 c2Alias = null;
var result = session.QueryOver<Parent>()
.InnerJoin(x => x.C1, () => c1Alias) // or use Left.JoinAlias
.InnerJoin(x => x.C2, () => c2Alias) // or use Left.JoinAlias
.Where(() => c1Alias.Id == 3)
.And(() => c2Alias.Id == 2)
.List();
I think you want a pair of Subqueries rather than a restriction, but you'll have to map the ParentID value in both Child1 and Child2.
Each SubQuery should return the ParentID where the childID is your search value, and then you can use a conjunction to return the Parent for both children, or null if there isn't one, I suppose.

NHibernate QueryOver, Projections and Aliases

I have an nhibernate issue where I am projecting the sql Coalesce function.
I am comparing two string properties having the same name, from two different entities. In the resulting sql, the same property from only the first entity is being compared thus:
var list = Projections.ProjectionList();
list.Add(
Projections.SqlFunction("Coalesce",
NHibernateUtil.String,
Projections.Property<TranslatedText>(tt => tt.ItemText),
Projections.Property<TextItem>(ti => ti.ItemText)));
var q = Session.QueryOver<TextItem>()
.Left.JoinQueryOver(ti => ti.TranslatedItems);
Evaluating q results in this sql
coalesce(this_.ItemText, this_.ItemText)
the this_ in the RHS needs to be an aliased table
I can use Projections.Alias(Projections.Property<TranslatedText>(tt => tt.ItemText), "ttAlias") but am not sure how to map "ttAlias" in the JoinQueryOver.
I can create an alias there too, but can't see how to name it.
TranslatedText ttAlias = null;
...
JoinQueryOver(ti => ti.TranslatedItems, () => ttAlias)
Aliases are variables in QueryOver, like you showed in the JoinQueryOver call. Alias names (strings) should not be needed in QueryOver, they are for Criteria queries.
To the problem itself: I can't test it right now, but I think this should work:
Projections.Property(() => ttAlias.ItemText)
I used this topic as a resource while writing a unit test. This QueryOver works well and may help others with similar issues. QueryOver still struggles with property mapping to transformers using expressions. It's technically possible to remove "Id" but IMHO it hinders clarity.
The complete example is on GitHub
String LocalizedName = "LocalizedName";
//Build a set of columns with a coalese
ProjectionList plColumns = Projections.ProjectionList();
plColumns.Add(Projections.Property<Entity>(x => x.Id), "Id");
plColumns.Add(Projections.SqlFunction("coalesce",
NHibernateUtil.String,
Projections.Property<Entity>(x => x.EnglishName),
Projections.Property<Entity>(x => x.GermanName))
.WithAlias(() => LocalizedName));
ProjectionList plDistinct = Projections.ProjectionList();
plDistinct.Add(Projections.Distinct(plColumns));
//Make sure we parse and run without error
Assert.DoesNotThrow(() => session.QueryOver<Entity>()
.Select(plDistinct)
.TransformUsing(Transformers.AliasToBean<LocalizedEntity>())
.OrderByAlias(() => LocalizedName).Asc
.Skip(10)
.Take(20)
.List<LocalizedEntity>());

NHibernate/LINQ - Aggregate query on subcollection

Querying child collections has been a recurring issue in our applications where we use NHibernate (via LINQ). I want to figure out how to do it right. I just tried forever to get this query to work efficiently using LINQ, and gave up. Can someone help me understand the best way to do something like this?
Model: ServiceProvider
HasMany->ServicesProvided
The gotcha here is that the HasMany is mapped as a component, so I can't directly query the ServicesProvided. For posterity's sake, here's the mapping:
public ServiceProviderMap()
{
DiscriminatorValue(ProfileType.SERVICE_PROVIDER.ID);
HasMany(p => p.ServicesProvided)
.Table("ServiceProvider_ServicesProvided")
.KeyColumn("ProfileID")
.Component(spMapping =>
{
spMapping.Map(service => service.ID)
.Not.Nullable();
})
.AsBag();
}
The query I am trying to create would return a collection of the count of each service that is provided. IE: Service1 -> 200, Service2 -> 465, etc.
I was able to get the query working using HQL, so here it is. Note that it just returns the ID of the service that is provided:
select service.ID, count(service)
from ServiceProvider as profile
inner join profile.ServicesProvided as service
group by service.ID
I was able to get the query "working" using LINQ, but it performed atrociously. Here's the code I used (warning - it's ugly).
Func<ServiceProvider, IEnumerable<ServicesProvided>> childSelector = sp => sp.ServicesProvided;
var counts = this._sessionManager.GetCurrentSession().Linq<ServiceProvider>()
.Expand("ServicesProvided")
.SelectMany(childSelector, (t, c) => new { t = t, c = c })
.Select(child => child.c)
.GroupBy(sp => sp.ID)
.Select(el => new { serviceID = el.Key, count = el.Count() });
I would love to learn how to do this correctly, please.
Short of going with HQL, the most elegant solution I can think of would be using a Criteria object. The following will give you what you need and with very low overhead:
ICriteria criteria = this._sessionManager.GetCurrentSession().CreateCriteria(typeof(ServiceProvider), "sp");
//set projections for the field and aggregate, making sure to group by the appropriate value
criteria.CreateAlias("sp.ServicesProvided", "s", JoinType.LeftOuterJoin)
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("s.ID"), "serviceID")
.Add(Projections.Count("sp.ID"), "count")
.Add(Projections.GroupProperty("s.ID")));
IList<object[]> results = criteria.List();
foreach (object[] entry in results)
{
int id = (int)entry[0], qty = (int)entry[1];
//Do stuff with the values
}