Is my IQueryable syntax correct? - asp.net-mvc

The generated SQL does a cross join but as the ID's are matched it acts like an inner join but is there a better performing way to do this by actually using the join keyword in C#? Is there a way where you don't have to specify how the properties join because they are all heirarchicly related anyway
Item is a Page class
PageRoles is an IQueryable property in a Page class
aspnet_Roles is an IQueryable property in a PageRole class
var item = _repository.GetByKey(999);
var f = (from fd in item.PageRoles
from k in fd.aspnet_Roles
where Roles.GetRolesForUser().Contains(k.RoleName)
&& k.RoleId == fd.RoleId
select k.RoleName)
.Count();
EDIT:
Here is an example of an IQueryable property in my classes. The below example comes from the PageRole class
public IQueryable<aspnet_Role> aspnet_Roles
{
get
{
var repo=NorthCadburyWebsite.Models.aspnet_Role.GetRepo();
return from items in repo.GetAll()
where items.RoleId == _RoleId
select items;
}
}

Does this compile or do you get an error?
In this case, as you mentioned, you don't need a join.
I haven't used this syntax, but it looks like you use k as a refererence (like an alias in sql syntax)
This should deliver the same result (without even using k):
var f = (from fd in item.PageRoles
where Roles.GetRolesForUser().Contains(fd.aspnet_Roles.RoleName)
&& fd.aspnet_Roles.RoleId == fd.RoleId
select fd.aspnet_Roles.RoleName)
.Count();
Note: Not tested, just a guess.

I don't have my Intellisense with me but I think you can:
var f = (from fd in item.PageRoles
join k in fd.aspnet_Roles on k.RoleId equals fd.RoleId
where Roles.GetRolesForUser().Contains(k.RoleName)
select k.RoleName)
.Count();

I think I'm confused, because I'm seeing the following hierarchy in your query which isn't making any sense:
[PageRoles]
|--RoleId <~~~~~~~~~.
|--[aspnet_Roles] |
|--RoleId ~~~~~~~*
|--RoleName
Is the hierarchy determined by the RoleId and you only mean for PageRoles.RoleId to reference aspnet_Roles.RoleId? So you might have something like:
[PageRoles] [aspnet_Roles]
|--RoleId |--RoleId
|--RoleName
And you're trying to make it into the following:
[PageRoles]
|--RoleId
|--[aspnet_Roles] (where RoleId == PageRoles.RoleId)
|--RoleName
...?
In which case if you can't do the following, then something is wrong with your IQueryable property definitions and not the query you have posted. Namely the method to get PageRoles's aspnet_Roles child rows is not set up properly.
var f = (from fd in item.PageRoles
from k in fd.aspnet_Roles
where Roles.GetRolesForUser().Contains(k.RoleName)
select k.RoleName)
.Count();
(fd.aspnet_Roles should already be constrained by RoleId and generate an inner join).
You can check your hierarchy by seeing what SQL is generated from:
var f = from fd in item.PageRoles
from k in fd.aspnet_Roles
select k;
which should vaguely resemble (with something having to do with item thrown in):
SELECT k.RoleName, k.SomethingElse
FROM PageRoles fd
INNER JOIN aspnetRoles k ON fd.RoleId = k.RoleId;
I'm sure I'm missing something, so please help me fill in the blanks...

Related

Entity Framework 6 vs Entity Framework Core Raw Sql

Entity Framework 6 example writing SQL queries for non-entity types:
context.Database.SqlQuery<string>(" ; with tempSet as " +
"(select " +
In Entity Framework 6, I can also write the following query with SqlQuery. How can I run the following query with Entity Framework Core?
; with tempSet as
(
select
transitionDatetime = l.transitionDate,
gateName = g.gateName,
staffid = l.staffid,
idx = row_number() over(partition by l.staffid order by l.transitionDate) -
row_number() over(partition by l.staffid, cast(l.transitionDate as date) order by l.transitionDate),
transitionDate = cast(l.transitionDate as date)
from
logs l
inner join
staff s on l.staffid = s.staffid and staffType = 'Student'
join
gate g on g.gateid = l.gateid
), groupedSet as
(
select
t1.*,
FirstGateName = t2.gatename,
lastGateName = t3.gatename
from
(select
staffid,
mintransitionDate = min(transitionDatetime),
maxtransitionDate = case when count(1) > 1 then max(transitionDatetime) else null end,
transitionDate = max(transitionDate),
idx
from
tempSet
group by
staffid, idx) t1
left join
tempSet t2 on t1.idx = t2.idx
and t1.staffid = t2.staffid
and t1.mintransitionDate = t2.transitionDatetime
left join
tempSet t3 on t1.idx = t3.idx
and t1.staffid = t3.staffid
and t1.maxtransitionDate = t3.transitionDatetime
where
t1.transitionDate between #startdate and #enddate
)
select
t.*,
g.mintransitionDate,
g.maxtransitionDate,
g.FirstGateName,
g.LastGateName
from
groupedSet g
right join
(select
d,
staffid
from
(select top (select datediff(d, #startdate, #endDate))
d = dateadd(d, row_number() over(order by (select null)) - 1, #startDate)
from
sys.objects o1
cross join
sys.objects o2) tally
cross join
staff
where
staff.stafftype = 'Student') t on cast(t.d as date) = cast(g.transitionDate as date)
and t.staffid = g.staffid
order by
t.d asc, t.staffid asc
How can I do with Entity Framework Core? Writing SQL queries for non-entity types?
I have done the 'fromsql' off of the context directly when it is a single table, but I realize this is not what you want but it builds on it.
var blogs = context.Blogs
.FromSql("SELECT * FROM dbo.Blogs")
.ToList();
However in a case like yours it is complex and a joining of multiple tables and CTEs. I would suggest you create a custom object, POCO C# in code, and assign it a DbSet<> in your model builder. Then you can do something like this:
var custom = context.YOURCUSTOMOBJECT.FromSql("(crazy long SQL)").ToList();
If your return matches the type it may work. I did something similar and just wrapped my whole method in a procedure. However EF Core you need to make a migration manually up and then add the creation of the proc manually in the 'Up' method of the migration if you wish to deploy it. If you went that route your proc would need to exist on the server already or deploy it like said above and do something similar to this:
context.pGetResult.FromSql("pGetResult #p0, #p1, #p2", parameters: new[] { "Flight", null, null }).ToList()
The important thing to note is you need to create a DBSet object first in your model context so the context you are calling knows the well typed object it is returning from direct SQL. It must match EXACTLY the columns and types being returned.
EDIT 3-8
To be sure you need to do a few steps I will write out:
A POCO class that has a Data Annotation of [Key] above a distinct property. This class matches your columns of what a procedure returns exactly.
A DBSet<(POCO)> in your context.
Create a new Migration with: "Dotnet ef Migrations add 'yourname'"
Observe the new migration scripts. If anything generating a table for the POCO gets created, erase it. You don't need it. This is for a result set not storage in the database.
Change the 'Up' section to manually script your SQL to the database something like below. Also ensure you drop the data if you ever want to revert in the 'Down' section
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"create proc POCONameAbove" +
"( #param1 varchar(16), #Param2 int) as " +
"BEGIN " +
"Select * " +
"From Table "
"Where param1 = #param1 " +
" AND param2 = #param2 "
"END"
);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("drop proc POCONameAbove");
}
So now you essentially hijacked the migration to do explicitly what you want. Test it out by deploying the changes to the database with "dotnet ef database update 'yourmigrationname'".
Observe the database, it should have your proc if the database update succeeded and you did not accidentally create a table in your migration.
The section you said you didn't understand is what gets the data in EF Core. Let's break it up:
context.pGetResult.FromSql("pGetResult #p0, #p1, #p2", parameters: new[] { "Flight", null, null }).ToList()
context.pGetResult = is using the DbSet you made up. It keeps you well typed to your proc.
.FromSQL( = telling the context you are going to do some SQL directly in the string.
"pGetResult #p0, #p1, #p2" = I am naming a procedure in the database that has three params.
, parameters: new[] { "Flight", null, null }) = I am just doing an array of objects that is in order of the parameters as needed. You need to match the SQL types of course but provided that is okay it will be fine.
.ToListAsync() = I want a collection and my goto is always ToList when debugging something.
Hope that helps. Once I learned this would work it opened up a whole other world of what I could do. You can take a look at a project I have done that is unfinished for reference. I hard coded a controller to show the proc with preset values. But it could be changed easily to just inject them in the api.
https://github.com/djangojazz/EFCoreTest/tree/master/EFCoreCodeFirstScaffolding

Cannot figure out this complex query

I have three tables and I am having trouble getting to the bottom of this one.
So I am given the ID of a vehicle, and I need to write a query to output a list of its seat rows.
Vehicle
{
id,
vehicleTypeId,
color
}
VehicleType
{
id,
type,
}
VehicleSeats
{
id,
description
}
This is as far as my query has gotten and I am just as a complete loss on getting this one out right. I need it to output a list of seats, not a list of types, I just do not know how to take it deeper.
var vehicleSeatsList = (from c in db.Vehicle
where c.VehicleTypeID == id
select c).ToList();
Here was my final solution. I have yet to plug it in to know if it is right. I was being a dummy. Dont know why I wasn't thinking of just doing a join...
var VehicleTypeSeats = (from c in db.VehicleTypeSeats
join a in db.Vehicles on c.AircraftTypeID equals a.VehicleTypeID
where c.VehicleTypeID == id
select c).ToList();
Assuming this is in SQL, there is no relationship between your VehicleSeats and Vehicle tables. You would need to add VehicleSeats.vehicleID or something like that. If you did, the below would work:
SELECT VehicleSeats.description
FROM VehicleSeats, Vehicle
WHERE VehicleSeats.vehicleID = Vehicle.id
AND Vehicle.id = {your given vehicle ID};
Simply inner join Vehicle and Vehicle seats on their common column then add the where statement.
var VehicleTypeSeats = (from c in db.VehicleTypeSeats
join a in db.Vehicles on c.AircraftTypeID equals a.VehicleTypeID
where c.VehicleTypeID == id
select c).ToList();

How to deal with Null Values in OUTER JOINS using Entity Framework 6

I need to process the query at the bottom of this post in C#. The query works, but I don't know how to use it in EF6. I used a method and a viewmodel for it (variable query = the query below). But when it encounters null values in the OUTER JOIN, int32 cant accept this value when calling .toList(). What's the best way to deal with it?
var result = context.Database.SqlQuery<TourQueryViewModel>(query);
var reslist = result.ToList();
I tried my first steps with LINQ, but I dont get it how to translate into LINQ itself or the query-methods, that are equivalent to it. So I am hoping for your help.
SELECT toursdata.TourId AS TourId, toursdata.Tourname AS Tourname,toursdata.Tourdate Tourdate,
toursdata.VehicleId AS VehicleId, toursdata.VehicleName AS VehicleName, toursdata.LicenseNumber AS LicenseNumber,
Employees.EmployeeId AS EmployeeId, Employees.Gender AS Gender, Employees.Forename AS Forename, Employees.Surname AS Surname
FROM (
SELECT te.TourId, te.Tourname, te.Tourdate,
Vehicles.VehicleId, Vehicles.VehicleName, Vehicles.LicenseNumber,
TourEmployees.EmployeeId
FROM TourEmployees RIGHT OUTER JOIN Tours AS te ON TourEmployees.TourId = te.TourId,
Tours AS tv INNER JOIN Vehicles ON tv.VehicleId = Vehicles.VehicleId
WHERE tv.TourId = te.TourId
) toursdata
LEFT OUTER JOIN Employees ON toursdata.EmployeeId = Employees.EmployeeId
To eliminate the null problem, I changed the data type of the corresponding entity-attribute to a nullable-type
int turned to int?.
Didnt know about that language feature.

LINQ to entity, wrong join type

I have a query that looks like so....
var q = Dal.TBLINVENTORies.Where(i => i.SHOWIT);
q = q.Where(i => i.dtStart < DateTime.Now || i.dtStart == null);
q = q.Where(i => i.dtEnd > DateTime.Now || i.dtEnd == null);
q = q.Where(i => i.sSystem.Contains("OE"));
q = q.Where(i => i.WS_ActiveList_ID == 0 || i.tblWS_ActiveList.WS_MasterList_ID == 16);
var test2 = q.ToList();
Immediately before the "ToList()", if I examine the query, I get the following sql (more or less)
SELECT [Extent1].*
FROM [dbo].[TBLINVENTORY] AS [Extent1]
INNER JOIN [dbo].[tblWS_ActiveList] AS [Extent2] ON [Extent1].[WS_ActiveList_ID] = [Extent2].[ID]
WHERE ([Extent1].[SHOWIT] = 1)
AND (([Extent1].[dtStart] < CAST( SysDateTime() AS datetime2)) OR ([Extent1].[dtStart] IS NULL))
AND (([Extent1].[dtEnd] > CAST( SysDateTime() AS datetime2)) OR ([Extent1].[dtEnd] IS NULL))
AND ([Extent1].[sSystem] LIKE '%OE%')
AND ([Extent1].[WS_ActiveList_ID] = 0 OR [Extent2].[WS_MasterList_ID] = 16)
Unfortunately, this is not what I need, because relationship between "Inventory" and "ActiveList" is not really 1-to-Many, but Zero-to-Many (I'm not sure I'm using the correct terms). Basically, An inventory item might or might not have a related "ActiveList".
If I change that raw SQL to use a LEFT OUTER JOIN, instead of an INNER JOIN, the SQL returns the values I expect.
What is needed to force the LEFT OUTER JOIN?
I've tried the recommended solution from Linq to entities - One to many relationship - need left outer join instead of cross join , but,
var q2 = from inv in Dal.TBLINVENTORies from al in inv.tblWS_ActiveList
returns an error:
Error 65 An expression of type 'xxxx.DAL.tblWS_ActiveList' is not allowed in a subsequent from clause in a query expression with source type 'System.Data.Entity.DbSet<xxxx.DAL.TBLINVENTORY>'. Type inference failed in the call to 'SelectMany'.
I wonder if my link/relationship is constructed incorrectly? Any other ideas?
Thanks!
EDIT :: Additional Data
-- create foreign key, but don't enforce on existing values
ALTER TABLE [dbo].[tblInventory] --the ONE Table
WITH NOCHECK
ADD CONSTRAINT [FK__tblInventory.WS_ActiveList_ID__tblWS_ActiveList.ID]
FOREIGN KEY([WS_ActiveList_ID])
REFERENCES [dbo].[tblWS_ActiveList] ([ID]) --the MANY Table
NOT FOR REPLICATION
GO
-- disable enforcement of the foreign key, but leave it in place (virtual key)
ALTER TABLE [dbo].[tblInventory]
NOCHECK CONSTRAINT [FK__tblInventory.WS_ActiveList_ID__tblWS_ActiveList.ID]
GO
and the definition of WS_ActiveList_ID:
[WS_ActiveList_ID] [int] NOT NULL CONSTRAINT [DF_TBLINVENTORY_WS_ActiveList_ID] DEFAULT (0),
Your main problem is that you've turned off the referential integrity checks in your database.
Apart from the obvious problem of bad data, this won't work with EF.
By far the best option is to make WS_ActiveList_ID nullable, update your data to change all the 0s to NULLs and turn the constraint back on.
If you can't do that, I think you'll have to generate a SQL statement as a string and execute it with dbContext.Database.SqlQuery<T> ( MSDN )

linq operators like in

HI,
I have 3 tables: Clips, Books and relationships between ClipBook
Problem is:
i need get book that has bookID=4 with some clips i mean many-to-many
in simple text sql it will be something like this:
select * from Clips where clipID in (select clipID from ClipBook where bookID=4)
Question is:
How can i do this with Linq without operator Join of course
this could be a solution;
from cb in ClipBooks
where cb.BookID == 4
select cb.Clip;
or
ClipBooks.Where(cb => cb.BookId == 4).Select(cb => cb.Clip);
the Contains method is in Linq converted into the IN operator, example:
String[] cities = {"London", "Paris", "Madrid"};
from p in context.Person
where cities.Contains(p.Adress.City)
select p
is translated into a sql clause like: .. WHERE a.City in ('London', 'Paris', 'Madrid') where a is the Address table alias after the join with the Person table.
edit:
you could write a single query without a subquery, but this will be converted to a join most probably:
var clips = (from c in context.ClipBooks
where c.BookID == 4
select c.Clip).Distinct();
or
var clips = from c in context.Clip
where c.ClicBooks.Any( cb => cb.BookID == 4)
select c
Any is translated in Exists()

Resources