Translate this Sql query into NHibernate Linq or Criteria? - nhibernate

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.

Related

Complex SQL Query in Rails 4

I have a complicated query I need for a scope in my Rails app and have tried a lot of things with no luck. I've resorted to raw SQL via find_by_sql but wondering if any gurus wanted to take a shot. I will simplify the verbiage a bit for clarity, but the problem should be stated accurately.
I have Users. Users own many Records. One of them is marked current (#is_current = true) and the rest are not. Each CoiRecord has many Relationships. Relationships have a value for when they were active (active_when) which takes four values, [1..4].
Values 1 and 2 are considered recent. Values 3 and 4 are not.
The problem was ultimately to have a scopes (has_recent_relationships and has_no_recent_relationships) on User that filters on whether or not they have recent Relationships on current Record. (Old Records are irrelevant for this.) I tried create a recent and not_recent scope on Relationship, and then building the scopes on Record, combining with checking for is_current == 1. Here is where I failed. I have to move on with the app but have no choice but to use raw SQL and continue the app, hoping to revisit this later. I put that on User, the only context I really need it, and set aside the code for the scopes on the other objects.
The SQL that works, that correctly finds the Users who have recent relationships is below. The other just uses "= 0" instead "> 0" in the HAVING clause.
SELECT * FROM users WHERE `users`.`id` IN (
SELECT
records.owner_id
FROM `coi_records`
LEFT OUTER JOIN `relationships` ON `relationships`.`record_id` = `records`.`id`
WHERE `records`.`is_current` = 1
HAVING (
SELECT count(*)
FROM relationships
WHERE ((record_id = records.id) AND ((active_when = 1) OR (active_when = 2)))
) > 0
)
My instincts tell me this is complicated enough that my modeling probably could be redesigned and simplified, but the individual objects are pretty simple, just getting at this specific data from two objects away has become complicated.
Anyway, I'd appreciate any thoughts. I'm not expecting a full solution because, ick. Just thought the masochists among you might find this amusing.
Have you tried using Arel directly and this website?
Just copy-and-pasting your query you get this:
User.select(Arel.star).where(
User.arel_table[:id].in(
Relationship.select(Arel.star.count).where(
Arel::Nodes::Group.new(
Relationship.arel_table[:record_id].eq(Record.arel_table[:id]).and(
Relationship.arel_table[:active_when].eq(1).or(Relationship.arel_table[:active_when].eq(2))
)
)
).joins(
CoiRecord.arel_table.join(Relationship.arel_table, Arel::Nodes::OuterJoin).on(
Relationship.arel_table[:record_id].eq(Record.arel_table[:id])
).join_sources
).ast
)
)
I managed to find a way to create what I needed which returns ActiveRelationship objects, which simplifies a lot of other code. Here's what I came up with. This might not scale well, but this app will probably not end up with so much data that it will be a problem.
I created two scope methods. The second depends on the first to simplify things:
def self.has_recent_relationships
joins(records_owned: :relationships)
.merge(Record.current)
.where("(active_when = 1) OR (active_when = 2)")
.distinct
end
def self.has_no_recent_relationships
users_with_recent_relationships = User.has_recent_relationships.pluck(:id)
if users_with_recent_relationships.length == 0
User.all
else
User.where("id not in (?)", users_with_recent_relationships.to_a)
end
end
The first finds Users with recent relationships by just joining Record, merging with a scope that selects current records (should be only one), and looks for the correct active_when values. Easy enough.
The second method finds Users who DO have recent relationships (using the first method.) If there are none, then all Users are in the set of those with no recent relationships, and I return User.all (this will really never happen in the wild, but in theory it could.) Otherwise I return the inverse of those who do have recent relationships, using the SQL keywords NOT IN and an array. It's this part that could be non-performant if the array gets large, but I'm going with it for the moment.

nhibernate linq: projection to DTO and columns

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.

Linq to NHibernate and Group By

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

Django - finding the extreme member of each group

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.

(N)Hibernate Auto-Join

I'm developing a web- application using NHibernate. Can you tell me how to write a NHibernate Query for the following SQL query:
SELECT v1.Id
FROM VIEW v1
LEFT JOIN VIEW v2 ON v1.SourceView = v2.Id
ORDER BY v1.Position
It's basically a auto-join but I don't know how to write this in Nhibernate. Lets say the property names are the same as the table column names.
You could just perform the select on the original entity and make the association between the two objects "lazy = false". As long as the entities are mapped then both will be returned and you wont get a lazyloadingexception when trying to access the object.
If you don't want to map "lazy=false" then you can also iterate through the results and perform some sort of operation (such as asking if it is null; if(v1.AssocatedObject == null){}) to ensure the data is loaded while the session is open.
Update:
I think there is actually a better one than that in, NHibernateUtil.Initialise() that can initialise a collection without having to wander through it.