How to change multiple queries to a single query? - c#

I have three tables namely "Projects", "Platforms", "Details".
I have collected detail from the Projects table, then Platforms table and Details table. From the details table we may get the multiple values or single value.
I have written the Linq query like below:
using (PEntities CSProject = new PEntities())
{
projectId = (from _project in CSProject.Projects
where _project.ProjectName == project
select _project.ProjectId).SingleOrDefault();
platformId = (from platformID in CSProject.Projects
where platformID.ProjectName == project
select platformID.PlatformId).SingleOrDefault();
platformName = (from platfrmName in CSProject.Platforms
where platfrmName.PlatformId == platformId
select platfrmName.PlatformName).SingleOrDefault();
cSProjectId = (from _csproject in CSProject.Details
where _csproject.ProjectId == projectId
select _csproject.CsprojectId).ToList<long?>();
}
Can you please help me out to write the above all query in a single single line?

You could create a correlated subquery for each of the values that you want to get in the select statement, like so (in SQL):
select
(select ProjectId from Projects where ProjectName = #project) as ProjectId,
(select PlatformId from Projects where ProjectName = #project) as PlatformId,
...
In SQL, the above doesn't need a table to query against (it will return one row), but you'll need something in LINQ-to-Entities, which will force you to query against a context, make sure there is at least one value in the table, etc.
Needless to say, it's not worth it, and while you'll gain less calls to the database, the actual query that is sent will be very complex.
That said, you're better off doing one of two things. The first is to create a stored procedure that creates the correlated subqueries and returns one record like above and call that from LINQ-to-Entities.
The other, if you are really looking for one record that stores these values is to store them in a type. Here's how to do it in an anonymous type:
using (PEntities CSProject = new PEntities())
{
var results = new {
ProjectId = (
from _project in CSProject.Projects
where _project.ProjectName == project
select _project.ProjectId).SingleOrDefault(),
PlatformId = (
from platformID in CSProject.Projects
where platformID.ProjectName == project
select platformID.PlatformId).SingleOrDefault(),
PlatformName = (
from platfrmName in CSProject.Platforms
where platfrmName.PlatformId == platformId
select platfrmName.PlatformName).SingleOrDefault();
CsProjectId = (
from _csproject in CSProject.Details
where _csproject.ProjectId == projectId
select _csproject.CsprojectId).ToList<long?>();
};
}
You won't see a reduction in calls to the database, but if what you're going for is a single record with these values, then this will achieve it.

Related

Linq To Entities, Select multiple sets of data with one query

I've found plenty of info on how to select multiple result sets with stored procedures but nothing substantial on how to do so with a linq query.
For example, I can do sub-queries that return mulitple sets of results with something like
var query = (from school in context.Schools
where school.id == someId
select new
{
subSetA = (from student in context.Students
select student).ToList(),
subSetB = (from building in context.Buildings
select building).ToList(),
}).First();
query.subSetA; //Access subSetA
query.subSetB; //Access subSetB
Which works fine, but what if I just want to select both subSetA and subSetB without querying against the school table? I want to select two separate sets of data that gets sent to the server in one query.
Any information as to how to do this with EF 6 would be great.
Well, I'm sure there are many ways to do this, but if you want to avoid introducing a third DbSet into the mix...
var query = (from s in context.Students.Take(1)
select new
{
subSetA = context.Students.ToList(),
subSetB = context.Buildings.ToList(),
})
Then, you can use query.ToList() or maybe using query.Load() and working with context.Students.Local, etc. would work.

Conditional LINQ query on self-joining table with 2 sets of data

I am trying to figure out how to perform a conditional query on an Employees table to bring back all of their assigned Projects, but the caveat I don't quite understand on how to implement is that for every Employee, there is 0 to 1 EmployeeAssistant (self-joining entity). So when I select EmployeeID=2 and it has an EmployeeAssistantID=5, I would like to display all of the Projects for both of these individuals, i.e. the main Employee (EmployeeID=2) and their assistant (EmployeeID=5).
The tables look like:
Employees
- EmployeeID -- (Pkey)
- EmployeeAssistantID -- (Fkey to Employees.EmployeeID)
- other fields
-
Projects
- ProjectID -- (PKey)
- EmployeeID -- (Fkey to Employees.EmployeeID)
- other fields
I attempted the following in LINQ:
var projects = Projects.Include(proj => proj.Employee)
.Select(x => new
{
proj.ProjectID,
proj.ProjectName,
proj.Employee.Name
// ... not sure how to bring back another layer of projects for the EmployeeAssistant?
})
You can use the following Linq query to get the data you want:
var query = from e in Employees
join a in Employees on e.EmployeeAssistantID equals a.EmployeeID
where e.EmployeeID == 2
select new
{
EmployeeID = e.EmployeeID,
AssistantID = a.EmployeeID,
EmployeeProjects = Projects.Where(p => p.EmployeeID == e.EmployeeID),
AssistantProjects = Projects.Where(p => p.EmployeeID == a.EmployeeID)
};
The anonymous type returned by the query contains all of the data from both employee entities, as well as all of the Project data of each employee (some can be the same, others might differ).
I believe you mean to do the following:
var employeeIDs = new[] { myEmployee.EmployeeID, myEmployee.EmployeeAssistantID };
var projects = Projects.Where(p => employeeIds.Contains(p.EmployeeID));
This will grab all projects that both the employee and the assistant have done, given a previously grabbed Employee record (which I've called myEmployee).

linq-to-sql conditional read

I'm writing a query that looks like this:
var TheOutput = (from x in MyDC.MyTable
where.....
select new MyModel()
{
MyPropID = (from a in MyDC.MyOtherTable
where....
select a.ElementID).SingleOrDefault(),
MyPropData = (from a in MyDC.MyOtherTable
where a.ElementID == MyPropID
select a.ElementData).SingleOrDefault(),
}
I'm filling up MyModel with several properties from the database. Two of these properties are filled by reading another table. At the moment, I first read MyPropID to see if there's an element in the other table and then I read the other table again to get the data, regardless of whether or not an ID was retrieved.
How can I eliminate this second read if I know, from reading MyPropID and returning a null, that there's no data that matches the where a.ElementID == MyPropID clause.
Thanks.
var TheOutput = (from x in MyDC.MyTable
where.....
let id = (from a in MyDC.MyOtherTable
where....
select a.ElementID).SingleOrDefault()
select new MyModel()
{
MyPropID = id,
MyPropData = (from a in MyDC.MyOtherTable
where id != null && a.ElementID == id
select a.ElementData).SingleOrDefault()
}
If your code would create a single SQL statement from this query I do not think checking for null would matter. If this query would result in multiple SQL statements it might.

Is this LINQ Query "correct"?

I have the following LINQ query, that is returning the results that I expect, but it does not "feel" right.
Basically it is a left join. I need ALL records from the UserProfile table.
Then the LastWinnerDate is a single record from the winner table (possible multiple records) indicating the DateTime the last record was entered in that table for the user.
WinnerCount is the number of records for the user in the winner table (possible multiple records).
Video1 is basically a bool indicating there is, or is not a record for the user in the winner table matching on a third table Objective (should be 1 or 0 rows).
Quiz1 is same as Video 1 matching another record from Objective Table (should be 1 or 0 rows).
Video and Quiz is repeated 12 times because it is for a report to be displayed to a user listing all user records and indicate if they have met the objectives.
var objectiveIds = new List<int>();
objectiveIds.AddRange(GetObjectiveIds(objectiveName, false));
var q =
from up in MetaData.UserProfile
select new RankingDTO
{
UserId = up.UserID,
FirstName = up.FirstName,
LastName = up.LastName,
LastWinnerDate = (
from winner in MetaData.Winner
where objectiveIds.Contains(winner.ObjectiveID)
where winner.Active
where winner.UserID == up.UserID
orderby winner.CreatedOn descending
select winner.CreatedOn).First(),
WinnerCount = (
from winner in MetaData.Winner
where objectiveIds.Contains(winner.ObjectiveID)
where winner.Active
where winner.UserID == up.UserID
orderby winner.CreatedOn descending
select winner).Count(),
Video1 = (
from winner in MetaData.Winner
join o in MetaData.Objective on winner.ObjectiveID equals o.ObjectiveID
where o.ObjectiveNm == Constants.Promotions.SecVideo1
where winner.Active
where winner.UserID == up.UserID
select winner).Count(),
Quiz1 = (
from winner2 in MetaData.Winner
join o2 in MetaData.Objective on winner2.ObjectiveID equals o2.ObjectiveID
where o2.ObjectiveNm == Constants.Promotions.SecQuiz1
where winner2.Active
where winner2.UserID == up.UserID
select winner2).Count(),
};
You're repeating join winners table part several times. In order to avoid it you can break it into several consequent Selects. So instead of having one huge select, you can make two selects with lesser code. In your example I would first of all select winner2 variable before selecting other result properties:
var q1 =
from up in MetaData.UserProfile
select new {up,
winners = from winner in MetaData.Winner
where winner.Active
where winner.UserID == up.UserID
select winner};
var q = from upWinnerPair in q1
select new RankingDTO
{
UserId = upWinnerPair.up.UserID,
FirstName = upWinnerPair.up.FirstName,
LastName = upWinnerPair.up.LastName,
LastWinnerDate = /* Here you will have more simple and less repeatable code
using winners collection from "upWinnerPair.winners"*/
The query itself is pretty simple: just a main outer query and a series of subselects to retrieve actual column data. While it's not the most efficient means of querying the data you're after (joins and using windowing functions will likely get you better performance), it's the only real way to represent that query using either the query or expression syntax (windowing functions in SQL have no mapping in LINQ or the LINQ-supporting extension methods).
Note that you aren't doing any actual outer joins (left or right) in your code; you're creating subqueries to retrieve the column data. It might be worth looking at the actual SQL being generated by your query. You don't specify which ORM you're using (which would determine how to examine it client-side) or which database you're using (which would determine how to examine it server-side).
If you're using the ADO.NET Entity Framework, you can cast your query to an ObjectQuery and call ToTraceString().
If you're using SQL Server, you can use SQL Server Profiler (assuming you have access to it) to view the SQL being executed, or you can run a trace manually to do the same thing.
To perform an outer join in LINQ query syntax, do this:
Assuming we have two sources alpha and beta, each having a common Id property, you can select from alpha and perform a left join on beta in this way:
from a in alpha
join btemp in beta on a.Id equals btemp.Id into bleft
from b in bleft.DefaultIfEmpty()
select new { IdA = a.Id, IdB = b.Id }
Admittedly, the syntax is a little oblique. Nonetheless, it works and will be translated into something like this in SQL:
select
a.Id as IdA,
b.Id as Idb
from alpha a
left join beta b on a.Id = b.Id
It looks fine to me, though I could see why the multiple sub-queries could trigger inefficiency worries in the eyes of a coder.
Take a look at what SQL is produced though (I'm guessing you're running this against a database source from your saying "table" above), before you start worrying about that. The query providers can be pretty good at producing nice efficient SQL that in turn produces a good underlying database query, and if that's happening, then happy days (it will also give you another view on being sure of the correctness).

Advice on removing multiple sub-queries (which contains a join) by optimizing Linq To SQL query

I'm working on adding globalization to my product cataloge and I have made it work. However, I feel that the underlaying SQL query isn't performing as well as it could and I could need some advice on how to change my Linq To SQL query to make it more efficient.
The tables that are used
Product contains a unique id for each product. This column is called EntityID
TextTranslation contains the globalized text. The columns in this table are CultureID (a string), TextID (a reference to the text), Value (the actuall globalized text).
Text contains the mapping between a globalized text and a product. There is also a column which indicates which type of text it is (like name, description and so on)
TextType contains the definition (id, name and description) for a text type.
var culturedTexts =
from translation in ctx.TextTranslations
join text in ctx.Texts on translation.TextId equals text.TextId
where translation.CultureId == "en-EN"
select new
{
text.EntityId,
text.TextTypeId,
translation.Value,
};
var products =
from p in ctx.Products
let texts = culturedTexts.Where(i => i.EntityId == p.EntityId)
select new Model.Product
{
Description = texts.Where(c => c.TextTypeId == (int)TextType.Description).SingleOrDefault().Value,
Name = texts.Where(c => c.TextTypeId == (int)TextType.Name).SingleOrDefault().Value
};
When this is executed I get a query which looks like
SELECT (
SELECT [t1].[Value]
FROM [Common].[TextTranslation] AS [t1]
INNER JOIN [Common].[Text] AS [t2] ON [t1].[TextId] = [t2].[TextId]
WHERE ([t2].[TextTypeId] = 2) AND ([t2].[EntityId] = [t0].[EntityId]) AND ([t1].[CultureId] = 'sv-SE')
) AS [Description], (
SELECT [t3].[Value]
FROM [Common].[TextTranslation] AS [t3]
INNER JOIN [Common].[Text] AS [t4] ON [t3].[TextId] = [t4].[TextId]
WHERE ([t4].[TextTypeId] = 1) AND ([t4].[EntityId] = [t0].[EntityId]) AND ([t3].[CultureId] = 'sv-SE')
) AS [Name]
FROM [Catalog].[Product] AS [t0]
So each globalized text (Name, Description) in the LINQ query gets its own sub query and associated join. Is it possible to streamline this a bit and remove each text type getting its own join and subquery?
Well, given that they are getting different TextTypeId values, how would you prefer the TSQL to look? If you do a single JOIN, you'll have to put in a messy SELECT CASE or similar to discriminate between type "1" and type "2".
One option would be to to simply bring back all the suitable rows and do the final projection in-memory at the client, but to be honest I expect that the SQL optimizer will make light work of that TSQL anyway... especially if that query hits a good spanning index.

Categories