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
Related
I have the following code in SQL:
SELECT
(SELECT Form_Title
FROM Forms
WHERE Form_ID = (SELECT TOP 1 Form_ID
FROM [Form_Responses]
WHERE [Form_Response_ID] = [Form_Response_ID])) AS Form_Name
FROM
[FooDb].[dbo].[Submissions]
In essence, I am selecting data three tables "away" and nesting all these selections. So I select a field from one table, and use it to make selection in another.
I can't use vars in a view, so how do I make this remotely readable?
Edit - the pseudocode below is what I was going for with the SQL above and it works, but I don't know how to make it clean, and that's the question:
//Form_Response_ID exists on Submissions
SELECT Get_Query_Result(Form_Response_ID) as Form_Name
FROM Submissions
Get_Query_Result(Form_Response_ID)() {
return SELECT Form_Title FROM Forms WHERE Form_ID = Get_Form_Id(Form_Response_ID);
}
// Form_Responses has Form_Response_ID as well
Get_Form_Id(Form_Response_ID) {
return SELECT TOP 1 Form_ID FROM [Form_Responses] WHERE [Form_Response_ID] = Form_Response_ID
}
Most likely the query you are looking for is like below
select f.Form_Title as Form_Name
from
Forms f JOIN Form_Responses fr
on f.Form_ID=fr.Form_ID
JOIN Submissions s
on fr.Form_Response_ID=S.Form_Response_ID
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.
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.
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.
My brain seems to be mush right now! I am using LINQ to Entity, and I need to get some data from one table that does NOT exist in another table.
For example: I need the groupID, groupname and groupnumber from TABLE A where they do not exist in TABLE B. The groupID will exist in TABLE B, along with other relevant information. The tables do not have any relationship. In SQL it would be quite simply (there is a more elegant and efficient solution, but I want to paint a picture of what I need)
SELECT
GroupID,
GroupName,
GroupNumber,
FROM
TableA
WHERE
GroupID NOT IN (SELECT GroupID FROM TableB)
Is there an easy/elegant way to do this using the Entity Framework/LINQ to Entity?
Right now I have a bunch of queries hitting the db, then comparing, etc. It's pretty messy.
You could use any
var temp =context.TableA
.Where(x=>!context.TableB.Any(y=>y.GroupID!=x.GroupID))
.Select(x=>new { GroupID = x.GroupID, GroupName=x.GroupName, GroupNumber = x.GroupNumber}).ToList();
It depends upon how you've met them, which you don't show, but, generally:
var q = from a in Context.TableA
where !a.Group.TableBs.Any()
select new
{
GroupID = a.GroupID,
GroupName = a.GroupName,
GroupNumber = a.GroupNumber
};
#Nix - Your result set should have been either:
var temp =context.TableA
.Where(x=>context.TableB.Any(y=>y.GroupID != x.GroupID))
.Select(x=>new { GroupID = x.GroupID, GroupName=x.GroupName, GroupNumber = x.GroupNumber}).ToList();
Or
var temp =context.TableA
.Where(x=> ! context.TableB.Any(y=>y.GroupID == x.GroupID))
.Select(x=>new { GroupID = x.GroupID, GroupName=x.GroupName, GroupNumber = x.GroupNumber}).ToList();
But NOT both, like you wrote it.