NHibernate How to convert a SQL Query pulling from multiple tables to QueryOver - sql

Hello any NHibernate genius,
Some help/guidance would really be appreciated, I'm a bit stuck!
I have the following SQL query, which I'd like to convert to QueryOver and I'm not sure weather it can be done? Any help would be greatly appreciated.
SQL:
Insert Into #categoryAndItems
Select GlobalRateCategoryId,null,[Description],null, null,null, GlobalRateCategoryId, 2 From [GlobalRateCategory]
Insert Into #categoryAndItems
Select Id,Name, gr.[Description],un.[Description],null,gr.Formula,gr.GlobalRateCategoryId, 1 From dbo.[GlobalRateCategoryVariable] gr JOIN
dbo.UnitOfMeasure un on gr.UnitOfMeasureId = un.UnitOfMeasureId
JOIN [GlobalRateCategory] grc on grc.GlobalRateCategoryId = gr.GlobalRateCategoryId
Insert Into #categoryAndItems
Select gr.GlobalResourceId,null, gr.[Description], um.[Description], gr.Rate,grr.Formula, c.id, 0 From #categoryAndItems c JOIN
GlobalRateResource grr on c.id = grr.GlobalRateCategoryId JOIN
GlobalResource gr on grr.GlobalResourceId = gr.GlobalResourceId JOIN
UnitOfMeasure um on gr.UnitOfMeasureId = um.UnitOfMeasureId
Select * From #categoryAndItems
order by groupid,id
drop table #categoryAndItems

This cannot be converted to QueryOver, however why not use a Named Query and an XML embedded resource and then project into a DTO?
var results = Session
.GetNamedQuery("GetCategoriesAndItems")
.SetResultTransformer(
new AliasToBeanResultTransformer(typeof(CategoryAndItemsDto)));
return results.List<CategoryAndItemsDto>();
and your DTO:-
public class CategoryAndItemsDto{
public virtual int Id { get; set; }
public virtual string Description{ get; set; }
...
}
This is the embedded XML file....
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<sql-query name="GetCategoriesAndItems">
Insert Into #categoryAndItems
Select ...
Insert Into #categoryAndItems
Select ...
Insert Into #categoryAndItems
Select ...
Select * From #categoryAndItems
order by groupid,id
drop table #categoryAndItems
</sql-query>
</hibernate-mapping>
Make sure the ALL column names returned by your select * match exactly the columns in the DTO (remember it is case sensitive)
And the final bit of the jigsaw to tell fluent you have an XML file:-
sessionFactory = Fluently.Configure()
.Mappings(m =>
{
...
m.HbmMappings.AddFromAssemblyOf<SomeEntityMap>();
})
.BuildSessionFactory();
note: SomeEntityMap is a class in the assembly that contains the XML embedded resource.

Related

Could not find a setter for property in class

I'm trying to create Criteria as follow:
var criteria - session.CreateSQLQuery("select * from myTable where ID in (select id from tableB) AND ANOTHERID IN(select id from tableC)");
criteria.SetResultTransformer(Transformers.AliasToBean(typeof(myClass))).List<MyClass>().ToList()
And got the following exception:
Could not find a setter for property 'NAME' in class 'MYCLASS'
hbm property:
<property name="Name" type="string" column="NAME" not-null="true" update="true" insert="true"/>
.cs:
private string name;
public virtual string Name
{
get {return this.name;}
set { this.name=value;}
}
What I'm missing here?
Versions of NHibernate prior 5.0 had a bug where properties were matched case sensitively. In the 5.0+ this query would work.
AliasToBeanResultTransformer (Transformer.AliasToBean(...)) is intended to be used with "projections" and therefore does not use mappings to do the binding from columns to the class.
If you want to return an entity and want to leverage the mapping, you'll need to call AddEntity(...) on your query, like this:
var query = session.CreateSQLQuery("select * from myTable where ID in (select id from tableB) AND ANOTHERID IN(select id from tableC)");
query.AddEntity(typeof(myClass)).List<MyClass>();

How to union multiple tables and perform paging in DB with Nhibernate

Is there any way to implement union and paging in DB?
eg:
(select A.col1 As ColumnA, A.col2 as ColumnB, A.col3 as ColumnC from table1 as A)
union
(select B.col1 as ColumnA, B.col2 as ColumnB, B.col3 as ColumnC from table2 as B)
I've found several solutions to implement this function, but paging is performed in memory, not in DB.
1. Use SQL query
This has several drawbacks, the most glaring one is that LIMIT and OFFSET are not standardized
across databases (Oracle even requires subquery) - so if you change db, the query will also need to be changed.
// PetDso is not NHibernate mapped, no virtual..
public class PetDto
{
public string Name { get; set; }
public string Owner { get; set; }
public long Age { get; set; }
}
IList<PetDto> pets = session.CreateSQLQuery(#"
select NAME as Name, OWNER as Owner, AGE as Age from CAT
union
select DOG_NAME as Name, OWNER_NAME as Owner, AGE_IN_YEARS as Age from DOG
order by Name, Owner, Age LIMIT :returnedRows OFFSET :skipRows")
.SetParameter("skipRows", 1)
.SetParameter("returnedRows", 2)
.SetResultTransformer(new AliasToBeanResultTransformer(typeof(PetDto)))
.List<PetDto>();
2. Use view and map it
Create a view in db and map it (probably with <class mutable="false">). Be careful though, you will need an id for each row that won't change between queries, because NH is caching entities using id.
HQL doesn't support unions (NH-2710) and it should be the most complete way to query db.

Using Common Table Expressions with EntityFramework 5

I am using EF5 Code First and I have a standard Tree type structure which is a bunch of nested Folders. Some folders have Devices inside them. What I want to do is be able to select a folder and find all devices within that folder and its child folders.
After some digging it seems I need use Common Table Expressions in order to generate the data I need. Unfortunately I am really struggling with how to do this.
public class Folder
{
public virtual ICollection<Folder> Children { get; set; }
public virtual ICollection<Device> Devices { get; set; }
}
public class Device
{
public virtual ICollection<Folder> PresentInFolders { get; set; }
}
I have attempted to add a GetDevicesForFolderRecursively function based on the work on this MSDN article. The query is incomplete as folder.FolderId equals device.FolderID wont work in my situation because a device can be in more than one folder so it has a list of folders it's in.
public IQueryable<Device> GetDevicesForFolderRecursively(int folderID)
{
return from folder in this.FlatFolders
join device in this.Devices on folder.FolderId equals device.FolderID
where folder.FolderId == folderID
select device;
}
Once this is out the way, I also dont know how accurately construct the CTE. The CTE that the MSDN article creates looks like so but I dont know how to modify it for my needs and how this would work recursively?
context.Database.ExecuteSqlCommand(#"CREATE VIEW [dbo].[ManagerEmployees]
AS
WITH cte ( ManagerEmployeeID, EmployeeID )
AS ( SELECT EmployeeID ,
EmployeeID
FROM dbo.Employees
UNION ALL
SELECT e.EmployeeID ,
cte.EmployeeID
FROM cte
INNER JOIN dbo.Employees AS e ON e.ReportsToEmployeeID = cte.ManagerEmployeeID
)
SELECT ISNULL(EmployeeID, 0) AS ManagerEmployeeID ,
ISNULL(ManagerEmployeeID, 0) AS EmployeeID
FROM cte");
Update
I have managed to get the CTE query to this state but still doesnt quite work. It doesnt understand AS ( SELECT FolderId , [EstateManagerService].dbo.Devices.DeviceSerial and im not sure what values are meant to be in there?
CREATE VIEW [dbo].[ManagerEmployees]
AS
WITH cte ( FolderID, DeviceID )
AS ( SELECT FolderId ,
[EstateManagerService].dbo.Devices.DeviceSerial
FROM [EstateManagerService].dbo.Folders
UNION ALL
SELECT e.Folder_FolderId,
cte.FolderID
FROM cte
INNER JOIN [EstateManagerService].dbo.FolderDevices AS e ON e.Folder_FolderId = cte.FolderID
)
SELECT ISNULL(DeviceID, 0) AS FolderID ,
ISNULL(FolderID, 0) AS DeviceID
FROM cte

NHibernate Linking Table with Data

SQL 2008 | .NET 4.0 | NHibernate 3.1 | NHibernate.Castle 3.1 | Castle.Core 2.5.2
So I have a linking table with metadata, like the author of this question NHibernate Mapping a Many to Many with Data on Join Table
Initially, I mapped just like the answer to this question as it seemed the most parsimonious way to handle it. However, after turning on show_sql and observing what was going on, the ID lookups ended up yielding N+1 queries where N is the number of associations.
Observe this example database that is analogous to my actual data, defined in sql-like syntax
CREATE TABLE [User]
(
Id int PRIMARY KEY
)
CREATE TABLE UserPref
(
Id int PRIMARY KEY,
Name varchar(32)
)
CREATE TABLE UserPrefAssociation
(
UserId int,
PrefId int,
Value varchar(32)
)
I hacked the following code together with this User one-to-many object mapping IList<UserPrefAssociation> Preferences { get; set; }
public IDictionary<string, string> GeneratePrefDict()
{
return Preferences
.ToDictionary(i => i.UserPref.Name, i => i.Value);
}
Sure, this works great, but as mentioned before, each i.UserPref.Name, is an additional query to SQL.
After playing in SQL, I have found the query that accomplishes what I want. My question then becomes how can I do this with NHibernate?
SELECT UserPref.Name, UserPrefAssociation.Value
FROM [User]
INNER JOIN UserPrefAssociation ON [User].Id = UserPrefAssociation.UserId
INNER JOIN UserPref ON UserPrefAssociation.UserPrefId = UserPref.Id
WHERE [User].Id = 1
~~~~SOLVED~~~~~
using NHibernate.Linq;
...
public IDictionary<string, string> GeneratePrefDict(ISession s)
{
return
(from entry in s.Query<User_UserPref>()
where entry.User == this
select new
{
key = entry.UserPref.Name,
value = entry.Value
})
.ToDictionary(i => i.key, i => i.value);
}
Generates this SQL
NHibernate: select userpref1_.Name as col_0_0_, user_userp0_.Value as col_1_0_ f
rom User_UserPref user_userp0_ left outer join UserPref userpref1_ on user_userp
0_.UserPrefId=userpref1_.Id where user_userp0_.UserId=#p0;#p0 = 1 [Type: Int32 (
0)]
Which is better than N+1 queries, and solves my issue.
I think you can achieve what you are wanting with Futures and QueryOver. Take a look at the following article:
Fighting cartesian product (x-join) when using NHibernate 3.0.0
If you can't visualize how to accomplish what you need from the above I can tailor that example more to your needs.

NHibernate, how to map a property to a subselect

I currently have a legacy system that uses SPs exclusively for access to the DB. My domain object looks something like this:
public class User : EntityBase
{
public virtual string Name {get;set;}
public virtual string CreatedBy {get;set;}
public virtual DateTime CreatedDate {get;set;}
}
The SP I have that mapped this looked like this:
CREATE PROCEDURE getUser
{
#ID int
}
select
Name
,(SELECT TOP 1 UserName FROM AuditTrail WHERE EntityID = [User].[ID] AND EntityName = 'User' AND AuditActionID = 1 ORDER BY DateStamp) AS CreatedBy
,(SELECT TOP 1 DateStamp FROM AuditTrail WHERE EntityID = [User].[ID] AND EntityName = 'User' AND AuditActionID = 1 ORDER BY DateStamp) AS CreatedDate
FROM [User]
WHERE [User].ID = #ID
So, as you can see, the audit information is separated from the entity itself on the database and the CreatedBy/CreatedOn (and ModifiedBy/ModifiedOn) are stored in a separate table called AuditTrail. the AuditActionID field on the table specifies if it was a create/update.
How can I setup this mapping with NHibernate? I looked into JOIN but it doesn't give me the option to restrict by the additional values (and a join isn't what I want).
Also, if this is possible in Fluent NHibernate, that's a bonus but I'm fine with trying just standard NHibernate mapping config if it gets me there.
UPDATE:
I have found one way to do this, but I'm not a fan. I have setup a SQLQuery that reads the data and maps it back to an object. It works, but I'd love to do this via mapping. Is it even possible since the "values" from the database I'm mapping to is a subselect and not editable?
Solution:
Thanks to the tip from Diego, this was the final solution I found (using Fluent NHibernate, in my ClassMap file):
Map(x => x.CreatedBy).Formula("(SELECT TOP 1 AuditTrail.UserName FROM AuditTrail WHERE AuditTrail.EntityID = [ID] AND AuditTrail.EntityName = 'User' AND AuditTrail.AuditActionID = 1 ORDER BY AuditTrail.DateStamp)");
Map(x => x.CreatedDate).Formula("(SELECT TOP 1 AuditTrail.DateStamp FROM AuditTrail WHERE AuditTrail.EntityID = [ID] AND AuditTrail.EntityName = 'User' AND AuditTrail.AuditActionID = 1 ORDER BY AuditTrail.DateStamp)");
Thanks,
M
You can specify the select clause as the formula for your property.