I'm using the current Linq provider for NHibernate (version 2.1).
I have two entities: VideoGame and GameDeveloper, with a many-to-one
relationship between them.
I'm trying to perform a query of this sort, which counts the number of
video games each game developer has:
from v in session.Linq<VideoGame>()
group by v.Developer into developerGroup
select new {developerGroup.Key.Name, Count = developerGroup.Count()}
Enumerating this query causes an exception - "could not resolve
property Key of Entities.VideoGame".
Now, if I group by v.Developer.Id it works, but I can't select the
Name column and show it in the results. I could group by
v.Developer.Name, but it doesn't seem right, as two developers might
have the same name.
I know the current Linq provider is not being developed any more, but
would appreciate any advice on the situation.
How about
from v in session.Linq<VideoGame>()
group by v.Developer into developerGroup
select new {key = developerGroup.Key, count = developerGroup.Count()}
Seems like group by is broken in the 2.1 NHibernate LINQ provider. A while ago Steve Strong blogged that group by is in the trunk so if you are feeling adventurous enough and not willing to wait on 3.0 then that could be an option.
Or you could use a brute force solution something like this
from v in (from vg in session.Linq<VideoGame>() select vg).ToList()
group by v.Developer into developerGroup
select new {developerGroup.Key.Name, Count = developerGroup.Count()};
Related
I am writing a log viewer app in ASP.NET / C#. There is a report window, where it will be possible to check some information about the whole database. One kind of information there I want to display on the screen is the number of times each generator (an entity in my domain, not Firebirds sequence) appears in the table. How do I do that using COUNT ?
Do I have to :
Gather the key for each different generator
Run one query for each generator key using count
Display it somehow
Is there any way that I can do it without having to do two queries to the database? The database size can be HUGE, and having to query it "X" times where "X" is the number of generators would just suck.
I am using a Firebird database, is there any way to fetch this information from any metadata schema or there is no such thing available?
Basically, what I want is to count each occurrence of each generator in the table. Result would be something like : GENERATOR A:10 times,GENERATOR B:7 Times,Generator C:0 Times and so on.
If I understand your question correctly, it is a simple matter of using the GROUP BY clause, e.g.:
select
key,
count(*)
from generators
group by key;
Something like the query below should be sufficient (depending on your exact structure and requirements)
SELECT KEY, COUNT(*)
FROM YOUR_TABLE
GROUP BY KEY
I solved my problem using this simple Query:
SELECT GENERATOR_,count(*)
FROM EVENTSGENERAL GROUP BY GENERATOR_;
Thanks for those who helped me.
It took me 8 hours to come back and post the answer,because of the StackOverflow limitation to answer my own questions based in my reputation.
Is there a good explanation out there on what exactly lazy="extra" is capable of?
All the posts I've seen all just repeat the fact that it turns references to MyObject.ItsCollection.Count into select count(*) queries (assuming they're not loaded already).
I'd like to know if it's capable of more robust things, like turning MyObject.ItsCollection.Any(o => o.Whatever == 5) into a SELECT ...EXISTS query.
Section 18.1 of the docs only touches on it. I'm not an NH developer, so I can't really experiment with it and watch SQL Profiler without doing a bit of work getting everything set up; I'm just looking for some sort of reference describing what this feature is capable of.
Thank you!
for version 2.x it is only used to translate a collection.Count() into a select count and as far as i can see in the source, it will also allow the construct collection[5] to fetch that particular entity (with index 5) instead of hydrating the whole collection.
For version 3.x i didn't see anything related in the release notes
Just tried calling Any() on a Collection Customer.Orders mapped with lazy="extra"
customer.Orders.Any()
and the resulting SQL statement looked something like this (simplified):
SELECT *
FROM Order
WHERE CustomerId = 120
Whereas when calling
customer.Orders.Count > 0
the resulting SQL looked like this:
SELECT count(*)
FROM Order
WHERE CustomerId = 120
The lazy = extra allow to count the element of a collection without needing of fetching it, since the lazy entity is decorated with a proxy, when the client code ask for the .Count on the collection, a proper "select count" query is issued to the database. Without lazy=extra the collection is read from the database.
I have a security schema where certain entities are secured by having a SecureEntity reference. A SecureEntity has a collection of RolePermissions, each of which has an Allow flag and a Priority. The idea is to match the user's roles against the RolePermissions on the SecureEntity. For example, a user may be allowed by their lowest priority permission but denied by a higher one, so it is the highest one that we are interested in. In this example the root entity I am querying is called ProcessCategory.
(SecureRoleId is the match for the user's role; SecureRoleName is just a string description.)
Assume a user has roles (1,2) and the SecureEntity has RolePermissions:
SecureRoleId = 1, Priority = 0, Allow = true
SecureRoleId = 2, Priority = 1, Allow = false
In this case the entity would not be selected. But if the user only had role 1, the entity would be selected. Of course, the SecureEntity may contain a bunch of other roles that the user does not have and are irrelevant.
The sql code below works and does this: 'select the entity if the highest priority role permission that the user also has is Allow=true'. So it basically filters RolePermission on the users own roles (IN clause), sorts by Priority, and takes the highest one if that is an Allow.
Here is the Sql:
select pc.* from ProcessCategory pc
join SecureEntity se
join RolePermission rp on se.SecureEntityId = rp.SecureEntityId
on pc.SecureEntityId = se.SecureEntityId
where rp.RolePermissionId = (select top 1 RolePermissionId
from RolePermission
where Allow = 1
and SecureEntityId = se.SecureEntityId
and SecureRoleId in(0,1)
order by Priority desc)
There may be another way to write the above Sql but it does what I need. Ideally I would like to achieve this using NHibernate Linq or Criteria. I spent a few hours trying to get Linq to work and failed with various 'invalid operation' exceptions on the inner join to RolePermission. I don't have much experience with ICriteria or MultiCriteria and would be interested if anybody can help me.
Note that the Fluent mapping for the objects is straightforward:
<some-entity>.References(x => x.SecureEntity)
and
SecureEntity.HasMany(x => x.RolePermissions).Not.Inverse();
Okay. I couldn't get this to work using native NH Linq, although that doesn't mean that it is not possible. But I looked through all the NH unit tests for Linq and couldn't find anything equivalent.
To get it working I created a database function called UserHasPermission that does everything in:
on pc.SecureEntityId = se.SecureEntityId
where rp.RolePermissionId = (select top 1 RolePermissionId
from RolePermission
where Allow = 1
and SecureEntityId = se.SecureEntityId
and SecureRoleId in(0,1)
order by Priority desc)
This works with any kind of secured entity. I then mapped that function as an NH Linq function by following the instructions in this page: http://wordpress.primordialcode.com/index.php/2010/10/01/nhibernate-customize-linq-provider-user-defined-sql-functions/.
If you follow those instructions, you have to create a normal LinqToObjects extension in C# that has an identical signature to your database one. You can then do your NH Linq query like:
return base.Query<T>().Where(c => ((ISecureEntity)c)
.SecureEntity.Id
.UserHasPermissions(user.SecureRoleIdsCsv) == 1);
The only problem I found was that my original Sql function returned a bit, which I mapped to a NH Boolean type. However this produced a really strange bit of sql that had several "Where ''True'' = ''True''" clauses that blew up in Sql Server. So I changed the result to an integer and everything worked okay. A bit counter-intuitive, but...
Doing it this way allowed me to carry on transparently using Linq for all my queries, without affecting existing code, because it automatically prepended each query with the security check.
Note that I looked in the Rhino Security source code and it uses a multiple criteria that is much too complex for me to understand with my limited NH knowledge. If I had done it using CreateCriteria, could I have combined it with Linq though?
The instructions in the above link do not make it clear that when you have created your own Dialect that registers your Sql function, you have to make sure you reference it in your NH configuration file (or code), otherwise you will get some kind of 'unknown type' exception.
I've recently upgraded my Linq provider to the new AST one. (NH3 on NuGet)
With the previous provider I was using linq to do "inline projections
to my DTO"
e.g.
from o in Session.Query<MyObject>()
select new MyObjectDTO {
Name = o.Name,
SubName = o.OtherObject.Name,
Sub2NAme = o.OtherObject2.Name
}
and this would generate a
SELECT o.Name, sn1.Name, sn2.Name FROM .....
JOIN.... JOIN....
statement.
Once I upgraded my provider I found a lot of select statements being
fired off. (My projected object is more complex than above).
I have come accross Fetch/FetchMany, which might help with the number
of queries, but as far as I can tell it means the full object will
come back for each flattened field I require.
Is there a way I can get the smallest possible number of columns required for the projection to be selected, rather than loading the full object graph to the project with?
Thanks,
Chris
It must be something with your usage of the result (like iterating many times the IQueryable), something odd with the mappings, or some complexity that was removed from the example.
I just tried that exact query, and only one SQL statement was generated.
I've been playing around with the new aggregation functionality in the Django ORM, and there's a class of problem I think should be possible, but I can't seem to get it to work. The type of query I'm trying to generate is described here.
So, let's say I have the following models -
class ContactGroup(models.Model):
.... whatever ....
class Contact(models.Model):
group = models.ForeignKey(ContactGroup)
name = models.CharField(max_length=20)
email = models.EmailField()
...
class Record(models.Model):
contact = models.ForeignKey(Contact)
group = models.ForeignKey(ContactGroup)
record_date = models.DateTimeField(default=datetime.datetime.now)
... name, email, and other fields that are in Contact ...
So, each time a Contact is created or modified, a new Record is created that saves the information as it appears in the contact at that time, along with a timestamp. Now, I want a query that, for example, returns the most recent Record instance for every Contact associated to a ContactGroup. In pseudo-code:
group = ContactGroup.objects.get(...)
records_i_want = group.record_set.most_recent_record_for_every_contact()
Once I get this figured out, I just want to be able to throw a filter(record_date__lt=some_date) on the queryset, and get the information as it existed at some_date.
Anybody have any ideas?
edit: It seems I'm not really making myself clear. Using models like these, I want a way to do the following with pure django ORM (no extra()):
ContactGroup.record_set.extra(where=["history_date = (select max(history_date) from app_record r where r.id=app_record.id and r.history_date <= '2009-07-18')"])
Putting the subquery in the where clause is only one strategy for solving this problem, the others are pretty well covered by the first link I gave above. I know where-clause subselects are not possible without using extra(), but I thought perhaps one of the other ways was made possible by the new aggregation features.
It sounds like you want to keep records of changes to objects in Django.
Pro Django has a section in chapter 11 (Enhancing Applications) in which the author shows how to create a model that uses another model as a client that it tracks for inserts/deletes/updates.The model is generated dynamically from the client definition and relies on signals. The code shows most_recent() function but you could adapt this to obtain the object state on a particular date.
I assume it is the tracking in Django that is problematic, not the SQL to obtain this, right?
First of all, I'll point out that:
ContactGroup.record_set.extra(where=["history_date = (select max(history_date) from app_record r where r.id=app_record.id and r.history_date <= '2009-07-18')"])
will not get you the same effect as:
records_i_want = group.record_set.most_recent_record_for_every_contact()
The first query returns every record associated with a particular group (or associated with any of the contacts of a particular group) that has a record_date less than the date/ time specified in the extra. Run this on the shell and then do this to review the query django created:
from django.db import connection
connection.queries[-1]
which reveals:
'SELECT "contacts_record"."id", "contacts_record"."contact_id", "contacts_record"."group_id", "contacts_record"."record_date", "contacts_record"."name", "contacts_record"."email" FROM "contacts_record" WHERE "contacts_record"."group_id" = 1 AND record_date = (select max(record_date) from contacts_record r where r.id=contacts_record.id and r.record_date <= \'2009-07-18\')
Not exactly what you want, right?
Now the aggregation feature is used to retrieve aggregated data and not objects associated with aggregated data. So if you're trying to minimize number of queries executed using aggregation when trying to obtain group.record_set.most_recent_record_for_every_contact() you won't succeed.
Without using aggregation, you can get the most recent record for all contacts associated with a group using:
[x.record_set.all().order_by('-record_date')[0] for x in group.contact_set.all()]
Using aggregation, the closest I could get to that was:
group.record_set.values('contact').annotate(latest_date=Max('record_date'))
The latter returns a list of dictionaries like:
[{'contact': 1, 'latest_date': somedate }, {'contact': 2, 'latest_date': somedate }]
So one entry for for each contact in a given group and the latest record date associated with it.
Anyway, the minimum query number is probably 1 + # of contacts in a group. If you are interested obtaining the result using a single query, that is also possible, but you'll have to construct your models in a different way. But that's a totally different aspect of your problem.
I hope this will help you understand how to approach the problem using aggregation/ the regular ORM functions.