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.
Related
My ASP.Net application has the following Linq to SQL function to get a distinct list of height values from the product table.
public static List<string> getHeightList(string catID)
{
using (CategoriesClassesDataContext db = new CategoriesClassesDataContext())
{
var heightTable = (from p in db.Products
join cp in db.CatProducts on p.ProductID equals cp.ProductID
where p.Enabled == true && (p.CaseOnly == null || p.CaseOnly == false) && cp.CatID == catID
select new { Height = p.Height, sort = Convert.ToDecimal(p.Height.Replace("\"", "")) }).Distinct().OrderBy(s => s.sort);
List<string> heightList = new List<string>();
foreach (var s in heightTable)
{
heightList.Add(s.Height.ToString());
}
return heightList;
}
}
I ran Redgate SQL Monitor which shows that this query is using a lot of resources.
Redgate is also showing that I am running the following query:
select count(distinct [height]) from product p
join catproduct cp on p.productid = cp.productid
join cat c on cp.catid = c.catid
where p.enabled=1 and p.displayfilter = 1 and c.catid = 'C2-14'
My questions are:
A suggestion to change the function so that it uses less resources?
Also, how does linq to sql generate the above query from my function? (I did not write select count(distinct [height]) from product anywhere in the code)
There are 90,000 records in the products. This category which I am trying to get the distinct list of heights has 50,000 product records
Thank you in advance,
Nick
First of all your posted sql query and linq query doesn't match at all. it's not the LINQ query rather the underlying SQL query itself performing slow. Make sure, all the columns involved in JOIN ON clause and WHERE clause and ORDER BY clause are indexed properly in order to have a better execution plan; else you will end up getting a FULL Table Scan and a File Sort and query will deemed to perform slow.
The join multiplies the number of Products the query returns. To undo that, you apply Distinct at the end. It will certainly reduce db resources if you return unique Products right away:
var heightTable = (from p in db.Products
where p.CatProducts.Any(cp => cp.CatID == catID)
&& p.Enabled && (p.CaseOnly == null || !p.CaseOnly)
select new
{
Height = p.Height,
sort = Convert.ToDecimal(p.Height.Replace("\"", ""))
}).OrderBy(s => s.sort);
This changes the join into a where clause. It saves the db engine the trouble of deduplicating the result.
If that still performs poorly, you should try to do the conversion and ordering in memory, i.e. after receiving the raw results from the database.
As for the count. I don't know where it comes from. Such queries typically get generated by paging libraries such as PagedList, but I see no trace of that in your code.
Side note: you can return ...
heightList.Select(x => x.Height.ToString()).ToList()
... instead of creating the list yourself.
I have 3 tables. Orders, OrderItems and OrderItemServices. Each order can contain multiple OrderItems and each OrderItem can contain multiple OrderItemServices.
I want to get a list of data of all orders from these tables in Linq. I could write a join but how do I make an anonymous data type to in select clasue which can give me Order list in this hierarchy?
If I use navigation properties and then select OrderItemServices in side select clause shown below it would fire individual select query for each OrderItemService which I want to avoid.
from order in Orders
select new
{
ActiveOrders = order,
ActiveOrderItems =order.OrderItems,
ActiveServices = order.OrderItems.Select(o => o.OrderItemServices)
}
Is it possible to group each order with a structure of multiple items inside it and multiple services inside items?
Refer msdn to start on LINQ To SQL
To get data from three tables you can get idea from the following simple example
var data = (from product in context.Products
from department in context.Departments
from category in context.Categories
where product.DeptId == department.DeptId
&& product.CatId == category.CatId
select new
{
product.Code,
product.Name,
department.Name,
category.Name
}).Distinct.ToList();
You have to set up your context to use eager loading:
var context = new MyDataContext();
var options = new DataLoadOptions();
options.LoadWith<Orders>(x => x.OrderItems);
options.LoadWith<OrderItems>(x => x.OrderItemServices);
context.LoadOptions = options;
var query = from order in context.Orders // ... etc
Then sub items will be included in initial query result and won't cause additional requests to the database. This will use JOIN internally to retrieve all the data in one go. You can check generated SQL using SQL Server Profiler.
http://blog.stevensanderson.com/2007/12/02/linq-to-sql-lazy-and-eager-loading-hiccups/
I have a stored procedure which returns me some dates, as well as an Id which related to a specific row in a table.
Basically, I am getting a list of all scheduled transactions for all accounts within an account portfolio.
The stored procedure returns a row with an Id (for the scheduled transaction), and some dates which I have minded within the proc.
If my query began with:
from p in Context.scheduled_transactions
then this plan would have worked. But I don't want to get the items like that, because in the proc, I am doing a lot of work to create business dates etc. So, instead of bring back the EF model - my proc just brings back the ID. I was HOPING to do something like this:
var trans = (from p in Context.get_scheduled_payments_by_portfolio(portfolioId)
.Include("account")
.Include("cost_centre")
.Include("z_account_transaction_type")
.Include("z_payment_frequency_type")
.Include("transaction_sub_category")
.Include("transaction_sub_category.transaction_category")
.Include("third_party")
select p).ToList();
But, the EF can't use 'Include' as it doesn't know what I am bring back. Although the id is called 'scheduled_transaction_id' in the proc - EF doesn't know that (understandably).
Is there a way I can tell EF that the ID is for a scheduled_transaction_model - and then use the 'Include'?
Maybe I need to just call the proc, which returns me a list of my objects, which has the scheduled_transaction_id, and all the dates I calculated in the proc, and then somehow, use that List<> in another linq query that can join the other tables?
EDIT:
I might be onto something! This doesn't show a syntax error. Just need to create a new Type... Playing with this:
var trans = (from p in Context.get_scheduled_payments_by_portfolio(portfolioId)
join st in Context.scheduled_transaction
.Include("account")
.Include("cost_centre")
.Include("z_account_transaction_type")
.Include("z_payment_frequency_type")
.Include("transaction_sub_category")
.Include("transaction_sub_category.transaction_category")
.Include("third_party")
on p.scheduled_transaction_id equals st.id
select p).ToList();
var ids = Context.get_scheduled_payments_by_portfolio(portfolioId).ToList();
var trans = (from p in Context.scheduled_transaction
.Include("account")
.Include("cost_centre")
.Include("z_account_transaction_type")
.Include("z_payment_frequency_type")
.Include("transaction_sub_category")
.Include("transaction_sub_category.transaction_category")
.Include("third_party")
where ids.Contains(p.id)
select p).ToList();
Try Contains() method which will translated into SQL's IN(,,) statement.
The answer was, join the proc to the table I was using, and then I can use the .Include()
var trans = (from p in Context.get_scheduled_payments_by_portfolio(portfolioId)
join st in Context.scheduled_transaction
.Include("account")
.Include("cost_centre")
.Include("z_account_transaction_type")
.Include("z_payment_frequency_type")
.Include("transaction_sub_category")
.Include("transaction_sub_category.transaction_category")
.Include("third_party")
on p.scheduled_transaction_id equals st.id
select new {st, p}).ToList();
And then with the new type, I can itterate through the list, and build my objects.
I have two identical queries (but looking at different tables), using Entity-Framework, calling the Oracle database, one will be able to find the table, but the other does not.
using(CarContainer Cars = new CarContainer()) {
var carModel = from c in temp.Cars.OfType<BMW>() orderby c.ID select c.MODEL;
}
using(CarContainer Cars = new CarContainer()) {
var carModel = from c in temp.Cars.OfType<BENTLEY>() orderby c.ID select c.MODEL;
}
When I run the second query, it gives me "Oracle.DataAccess.Client.OracleException: ORA-00942: table or view does not exist"
I opened up SQL Plus, using the same credentials, and did a select * from BENTLEY and it gave me the table.
When I used my grants.sql file, one of the grant commands did not get successfully passed to the Oracle Database.
I added in GRANT ALL ON CHOWNER.BENTLEY TO ADMINROLE; and it worked again!
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).