NHibernate 3 - type safe way to select a distinct list of values - nhibernate

I am trying to select a distinct list of values from a table whilst ordering on another column.
The only thing working for me so far uses magic strings and an object array. Any better (type-safe) way?
var projectionList = Projections.ProjectionList();
projectionList.Add(Projections.Property("FolderName"));
projectionList.Add(Projections.Property("FolderOrder"));
var list = Session.QueryOver<T>()
.Where(d => d.Company.Id == SharePointContextHelper.Current.CurrentCompanyId)
.OrderBy(t => t.FolderOrder).Asc
.Select(Projections.Distinct(projectionList))
.List<object[]>()
.ToList();
return list.Select(l => new Folder((string)l[0])).ToList();
btw, doing it with linq won't work, you must select FolderOrder otherwise you'll get a sql error (ORDER BY items must appear in the select list if SELECT DISTINCT is specified.
)
and then doing that gives a known error : Expression type 'NhDistinctExpression' is not supported by this SelectClauseVisitor. regarding using anonymous types with distinct
var q = Session.Query<T>()
.Where(d => d.Company.Id == SharePointContextHelper.Current.CurrentCompanyId)
.OrderBy(d => d.FolderOrder)
.Select(d => new {d.FolderName, d.FolderOrder})
.Distinct();
return q.ToList().Select(f => new Folder(f));
All seems a lot of hoops and complexity to do some sql basics....

To resolve the type-safety issue, the syntax is:
var projectionList = Projections.ProjectionList();
projectionList.Add(Projections.Property<T>(d => d.FolderName));
projectionList.Add(Projections.Property<T>(d => d.FolderOrder));

the object [] thing is unavoidable, unless you define a special class / struct to hold just FolderName and FolderOrder.
see this great introduction to QueryOver for type-saftey, which is most certainly supported.
best of luck.

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.

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>());

LINQ to NHibernate: selecting entity which has a specific entity in a one to many association

I would like to make this query:
Session.Linq<User>().Where(u => u.Payments.Count(p => p.Date != null) > 0);
In plain English I want to get all the users that has at least one payment with the date specified.
When I run the sample code I get a System.ArgumentException with the message:
System.ArgumentException : Could not find a matching criteria info provider to: this.Id = sub.Id
Do you know a solution to this problem?
It would also be very helpful if someone could provide the same query with the NHibernate Query by Criteria API.
I'm not sure if this will work in your particular case, but I would use the .Any() extension to clean up the linq query a bit; for example:
Session.Linq<User>().Where(u => u.Payments.Any(p => p.Date != null));
I think something like it:
Customer customerAlias = null;
criteria = CurrentSession.CreateCriteria(typeof(User), () => customerAlias);
if (searchCriteria.OrdersNumber.HasValue)
{
ICriteria paymentsCriteria = criteria.CreateCriteria<Customer>(x => x.Payments);
DetachedCriteria paymentsCount = DetachedCriteria.For<Payment>();
paymentsCount.SetProjection(Projections.RowCount());
paymentsCount.Add(SqlExpression.NotNull<Payment>(x => x.Date));
paymentsCount.Add<Payment>(x => x.Customer.Id == customerAlias.Id);
paymentsCriteria.Add(Subqueries.Gt(1, paymentsCount));
}
return criteria.List<User>();

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
}

How do I make DBIx::Class join tables using other operators than `=`?

Summary
I've got a table of items that go in pairs. I'd like to self-join it so I can retrieve both sides of the pair in a single query. It's valid SQL (I think), the SQLite engine actually does accept it, but I'm having trouble getting DBIx::Class to bite the bullet.
Minimal example
package Schema::Half;
use parent 'DBIx::Class';
__PACKAGE__->load_components('Core');
__PACKAGE__->table('half');
__PACKAGE__->add_columns(
whole_id => { data_type => 'INTEGER' },
half_id => { data_type => 'CHAR' },
data => { data_type => 'TEXT' },
);
__PACKAGE__->has_one(dual => 'Schema::Half', {
'foreign.whole_id' => 'self.whole_id',
'foreign.half_id' => 'self.half_id',
# previous line results in a '='
# I'd like a '<>'
});
package Schema;
use parent 'DBIx::Class::Schema';
__PACKAGE__->register_class( 'Half', 'Schema::Half' );
package main;
unlink 'join.db';
my $s = Schema->connect('dbi:SQLite:join.db');
$s->deploy;
my $h = $s->resultset('Half');
$h->populate([
[qw/whole_id half_id data /],
[qw/1 L Bonnie/],
[qw/1 R Clyde /],
[qw/2 L Tom /],
[qw/2 R Jerry /],
[qw/3 L Batman/],
[qw/3 R Robin /],
]);
$h->search({ 'me.whole_id' => 42 }, { join => 'dual' })->first;
The last line generates the following SQL:
SELECT me.whole_id, me.half_id, me.data
FROM half me
JOIN half dual ON ( dual.half_id = me.half_id AND dual.whole_id = me.whole_id )
WHERE ( me.whole_id = ? )
I'm trying to use DBIx::Class join syntax to get a <> operator between dual.half_id and me.half_id, but haven't managed to so far.
Things I've tried
The documentation hints towards SQL::Abstract-like syntax.
I tried writing the has_one relationship as such:
__PACKAGE__->has_one(dual => 'Schema::Half', {
'foreign.whole_id' => 'self.whole_id',
'foreign.half_id' => { '<>' => 'self.half_id' },
});
# Invalid rel cond val HASH(0x959cc28)
Straight SQL behind a stringref doesn't make it either:
__PACKAGE__->has_one(dual => 'Schema::Half', {
'foreign.whole_id' => 'self.whole_id',
'foreign.half_id' => \'<> self.half_id',
});
# Invalid rel cond val SCALAR(0x96c10b8)
Workarounds and why they're insufficient to me
I could get the correct SQL to be generated with a complex search() invocation, and no defined relationship. It's quite ugly, with (too) much hardcoded SQL. It has to imitated in a non-factorable way for each specific case where the relationship is traversed.
I could work around the problem by adding an other_half_id column and joining with = on that. It's obviously redundant data.
I even tried to evade said redundancy by adding it through a dedicated view (CREATE VIEW AS SELECT *, opposite_of(side) AS dual FROM half...) Instead of the database schema it's the code that got redundant and ugly, moreso than the search()-based workaround. In the end I wasn't brave enough to get it working.
Wished SQL
Here's the kind of SQL I'm looking for. Please note it's only an example: I really want it done through a relationship so I can use it as a Half ResultSet accessor too in addition to a search()'s join clause.
sqlite> SELECT *
FROM half l
JOIN half r ON l.whole_id=r.whole_id AND l.half_id<>r.half_id
WHERE l.half_id='L';
1|L|Bonnie|1|R|Clyde
2|L|Tom|2|R|Jerry
3|L|Batman|3|R|Robin
Side notes
I really am joining to self in my full expanded case too, but I'm pretty sure it's not the problem. I kept it this way for the reduced case here because it also helps keeping the code size small.
I'm persisting on the join/relationship path instead of a complex search() because I've got multiple uses for the association, and I didn't find any "one size fits all" search expression.
Late update
Answering my own question two years later, it used to be a missing functionality that has since then been implemented.
For those still interested by this, it's finally been implemented as of 0.08192 or earlier. (I'm on 0.08192 currently)
One correct syntax would be:
__PACKAGE__->has_one(dual => 'Schema::Half', sub {
my $args = shift;
my ($foreign,$self) = #$args{qw(foreign_alias self_alias)};
return {
"$foreign.whole_id" => { -ident => "$self.whole_id" },
"$foreign.half_id" => { '<>' => { -ident => "$self.half_id" } },
}
});
Trackback: DBIx::Class Extended Relationships on fREW Schmidt's blog where I got to first read about it.
I think that you could do it by creating a new type of relationship extending DBIx::Class::Relationship::Base but it doesn't seem incredibly well documented. Have you considered the possibility of just adding a convenience method on the resultset set for Half that does a ->search({}, { join => ... } and returns the resultset from that to you? It's not introspectable like a relationship but other than that it works pretty much as well. It uses DBIC's ability to chain queries to your advantage.
JB, notice that instead of:
SELECT *
FROM half l
JOIN half r ON l.whole_id=r.whole_id AND l.half_id<>r.half_id
WHERE l.half_id='L';
You can write the same query using:
SELECT *
FROM half l
JOIN half r ON l.whole_id=r.whole_id
WHERE l.half_id<>r.half_id AND l.half_id='L';
Which will return the same data and is definitely easier to express using DBIx::Class.
Of course, this doesn't answer the question "How do I make DBIx::Class join tables using other operators than =?", but the example you showed doesn't justify such need.
Have you tried:
__PACKAGE__->has_one(dual => 'Schema::Half', {
'foreign.whole_id' => 'self.whole_id',
'foreign.half_id' => {'<>' => 'self.half_id'},
});
I believe the matching criteria in the relationship definition is the same used for searches.
Here is how to do it:
...
field => 1, # =
otherfield => { '>' => 2 }, # >
...
'foreign.half_id' => \'<> self.half_id'