Having a bit of problem understanding why EF (4.1) is generating a particular SQL query. Here goes:
Basically I have these two classes
public class Rota
{
public int RotaId { get; set; }
public int RotaGroupId { get; set; }
public virtual RotaGroup RotaGroup { get; set; }
public int EmployeeId { get; set; }
public virtual Employee Employee { get; set; }
...
and
public class RotaGroup
{
public int RotaGroupId { get; private set; }
public bool IsCurrentRota { get; set; }
...
The mappings for rota is as follows:
HasKey(r => r.RotaId);
Property(r=>r.RotaId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(r => r.RotaGroup).WithMany()
.HasForeignKey(r => r.RotaGroupId)
.WillCascadeOnDelete(false);
HasRequired(r => r.Employee).WithMany()
.HasForeignKey(r => r.EmployeeId)
.WillCascadeOnDelete(false);
...
Ok. Now the following linq query:
_context.Rotas.Include(r => r.RotaGroup)
.Where(r => r.EmployeeId == 1 && r.RotaGroup.IsCurrentRota)
.ToList();
Generates the following SQL:
SELECT ...columns...
FROM [dbo].[Rota] AS [Extent1]
INNER JOIN [dbo].[RotaGroup] AS [Extent2] ON [Extent1].[RotaGroupId] = [Extent2][RotaGroupId]
LEFT OUTER JOIN [dbo].[RotaGroup] AS [Extent3] ON [Extent1].[RotaGroupId] = [Extent3].[RotaGroupId]
WHERE ([Extent2].[IsCurrentRota] = 1) AND ([Extent1].[MyIgluUserId] = 1
Im sure you can see the issue. Why oh why is it joining on rotaGroup (as it should) and then doing a left outer join? Further more the columns from [Extent2] (the inner join) are not used. Only the columns from the left outer join bit ([Extent3]) are used.
[Extent2] is used - it is part of SQL's WHERE. The result of the query will be correct but the performance will be probably worse. I don't think SQL server will optimize this to get rid of unnecessary left join.
That is how EF generates queries. As I understand it, EF doesn't track usage of entity sets so those two query parts Include(r => r.RotaGroup) and Where(r => r.RotaGroup.IsCurrentRota) are not related to each other. Left join is result of Include and inner join is result of Where. You can try to modify query so that part with Include is subquery of filtering but I doubt it will work differently.
Running into a similar issue on my end, where multiple JOINs are generated when they are not needed (i.e. the SQL query could easily be altered to include just one INNER JOIN to a parent table instead of having both an INNER and LEFT JOIN to the parent table).
My problem was also about testing for equality to multiple values (e.g. where child.ParentID == 1 || child.ParentID == 2 || child.ParentID == 3), which created a messed up where clause (where tbl1 and tbl2 are the INNER and LEFT joined tables added to the SELECT statement):
WHERE tbl1.ParentID = 1 or tbl2.ParentID IN (2, 3)
Both of these issues have been corrected in the 2011 June CTP package:
Entity framework CTP - June 2011
Related
I have very simple models
.NET Core 2.1 / EF Core 2.1 / MSSQL
public class ImageZ
{
[Key]
public Guid Id { get; set; }
public string Base64 { get; set; }
public string Title { get; set; }
}
public class Gallery
{
[Key]
public Guid Id { get; set; }
public ImageZ MainImage { get; set; }
public List<ImageZ> Images { get; set; } = new List<ImageZ>();
}
and I'm using this LINQ to load it from db
return _context
.Gallery
.Include(x => x.Images)
.Include(x => x.MainImage)
.OrderBy(x => Guid.NewGuid())
.FirstOrDefault();
But it sends two queries to db
SELECT TOP(1) [x].[Id], [x].[MainImageId], [x.MainImage].[Id], [x.MainImage].[Base64], [x.MainImage].[GalleryId], [x.MainImage].[Title]
FROM [Gallery] AS [x]
LEFT JOIN [ImageZ] AS [x.MainImage] ON [x].[MainImageId] = [x.MainImage].[Id]
ORDER BY NEWID(), [x].[Id]
SELECT [x.Images].[Id], [x.Images].[Base64], [x.Images].[GalleryId], [x.Images].[Title]
FROM [ImageZ] AS [x.Images]
INNER JOIN (
SELECT DISTINCT [t].*
FROM (
SELECT TOP(1) [x0].[Id], NEWID() AS [c]
FROM [Gallery] AS [x0]
LEFT JOIN [ImageZ] AS [x.MainImage0] ON [x0].[MainImageId] = [x.MainImage0].[Id]
ORDER BY [c], [x0].[Id]
) AS [t]
) AS [t0] ON [x.Images].[GalleryId] = [t0].[Id]
ORDER BY [t0].[c], [t0].[Id]
Is it correct behaviour? shouldn't it be done just with one?
Do you really need to select * from MainImage and Images? The blanket include could have an impact on your index selection. With 2.2, you can now use a projection with .ToList() to only select the columns you need. It will still use separate queries for each child collection, but will be limited to the columns you project into.
Alternatively, since you are only selecting one row (at random due to the order by on New Guid), you might be able to issue the separate requests explicitly and not need the order by in the secondary query (over Images). Indeed since each query will be using a different newid(), I suspect your images results in this case won't be properly aligned and you may need to do it explicitly.
Is it possible to Load References when instead of using the code below:
SqlExpression<Customer> q = db.From<Customer>();
q.Join<Customer,CustomerAddress>((cust,address) => cust.Id == address.CustomerId);
List<Customer> dbCustomers = db.LoadSelect(q);
Using this:
public class KpiTotal : IKpiTotal
{
public DateTime Date { get; set; }
public int TeamId { get; set; }
public Team Team { get; set; }
public int AccountId { get; set; }
public Account Account { get; set; }
public double Total { get; set; }
}
var result = dbCon.SelectFmt<KpiTotal>(#"select convert(date, t.TransactionDate) [Date], tm.TeamId,a.AccountNumber, count(distinct(t.RequisitionNumber)) Total
from task.tblTransactions t
inner join task.tblRequisitions r on r.RequisitionNumber = t.RequisitionNumber
inner join task.tblAccounts a on a.AccountNumber = r.AccountNumber
inner join Team tm on tm.DivisionId = a.DivisionId
where t.TransactionTypeNumber = 201 and a.IsActive = 1
and t.TransactionDate between {0} and {1}
group by convert(date, t.TransactionDate), tm.TeamName, a.AccountName
order by 1,2 desc", dateRange.Start, dateRange.End);
Because my result object (KpiTotal) has references to two child tables, and I would like to automatic load the references, instead of getting it with a foreach block.
I'm assuming you want to load in Team and Account from the above query. The LoadSelect method sniffs the POCO model and generates a query that pulls back all related DB records based on the foreign key relationships to the core object you're querying. It generates a query similar to this for each referenced / joined POCO (very pseudo-coded):
SELECT * FROM Team /* Related POCO */
WHERE Team.Id IN (SELECT TeamId FROM [original query with WHERE clase])
Basically, it does a single query to bring back all Teamss or Accounts.
With ServiceStack.OrmLite v4.0.40, there is now a new Merge extension method that will stitch together object references based in a more manual process.
In your case, you can query your KpiTotal results, then run just two separate queries to fetch back Team and Account lists, then merge them in. Basically:
var result = dbCon.SelectFmt<KpiTotal>(/* gnarly SQL */);
var teams = dbCon.Select(/* get all relevant teams */);
var accounts = dbCon.Select(/* get all relevant accounts */);
result.Merge(teams);
result.Merge(accounts);
Debug.WriteLine(result.Dump()); // Output to console / debug window, whatever
I have two models, Benefit and SchemeName
Benefit -
[Key]
public int BenefitID { get; set; }
public string BenefitName { get; set; }
public string BenefitDescription { get; set; }
public virtual ICollection<SchemeName> SchemeNames { get; set; }
SchemeName
[Key]
public int SchemeNameID { get; set; }
public string Name { get; set; }
public virtual ICollection<Benefit> Benefits { get; set; }
This has created three tables in the database Benefits, SchemeNames and a joining table called SchemeNameBenefits.
I am trying to populate a droplownlist that contains only the SchemeNames associated with a certain Benefit but am not sure how I can do this, can I reference the join table in my code?
I started with the following (which returns all SchemeNames)
private void PopulatePensionSchemeName(object selectedPensionSchemeName = null)
{
var schemeNameQuery = from d in db.SchemeNames
orderby d.SchemeNameID
select d;
ViewBag.PensionSchemeNameID = new SelectList(schemeNameQuery, "SchemeNameID", "Name", selectedPensionSchemeName);
}
But I'm not sure how I can add this clause. Any pointers?
You'll need the key of the Benefit object you want the SchemeNames for. The query you're probably looking for is:-
var benefitId = // However you get your benefit Id
var schemaNameQuery = from b in db.Benefits
from s in b.SchemeNames
where b.BenefitId == benefitId
select s;
Or in the extension method syntax:-
var schemaNameQuery = db.Benefits.Where(b.BenefitId == benefitId)
.SelectMany(b => b.SchemeNames);
Which produces the following SQL:-
SELECT ...
FROM [dbo].[SchemeNameBenefits] AS [Extent1]
INNER JOIN [dbo].[SchemeNames] AS [Extent2]
ON [Extent1].[SchemeName_Id] = [Extent2].[SchemeNameId]
WHERE [Extent1].[Benefit_Id] = #p__linq__0
Alternately you can use:-
var benefitId = // However you get your benefit Id
var schemeNameQuery = from d in db.SchemeNames
where d.Benefits.Any(x => x.Id == benefitId)
orderby d.SchemeNameId
select d;
This produces the following SQL:-
SELECT ...
FROM ( SELECT ... FROM [dbo].[SchemeNames] AS [Extent1]
WHERE EXISTS (SELECT 1 AS [C1]
FROM [dbo].[SchemeNameBenefits] AS [Extent2]
WHERE ([Extent1].[SchemeNameId] = [Extent2].[SchemeName_Id])
AND ([Extent2].[Benefit_Id] = #p__linq__0)))
AS ...
ORDER BY [Project2].[Id] ASC
Note that in both cases the generated SQL references your junction table even though it isn't part of your EF model.
If you already have the Benefit object, of course, you can get its SchemeNames more simply by using:-
var schemeNameQuery = benefit.SchemeNames;
I would do this by using a Junction Table. The Junction Table is your Joining Table. It will consist of two foreign Keys, SchemeNameID & BenefitID.
Check out this website for more on Junction Tables:
http://megocode3.wordpress.com/2008/01/04/understanding-a-sql-junction-table/
It helped me out a lot.
[Disclaimer] Conditional on there existing or adding the SchemeNameBenefits model the following should work.
So it seems like the SchemeNameBenefits table is a many-to-many map, in this case use the benefit ID to get a collection of scheme ID's
var schemeIds = db.SchemeNamesBenefits.Where(map => map.BenefitID == id)
.Select(map => map.SchemeNameID).ToArray();
Then pull back all the scheme name information for these scheme ID's
var result = db.SchemeNames.Where(scheme => schemeIds.Contains(scheme.SchemeNameID))
.OrderBy(scheme => scheme.SchemeNameId)
.Select(scheme => scheme.Name).ToArray();
Or in one query
var result = db.SchemeNamesBenefits.Where(map => map.BenefitID == id)
.SelectMany(map => db.SchemeNames
.Where(scheme => map.SchemeNameID == scheme.SchemeNameID)
.OrderBy(scheme => scheme.SchemeNameId)
.Select(scheme => scheme.Name)
.AsEnumerable())
.ToArray()
I have two entity classes:
public class Invoice
{
public int ID { get; set;}
public int Amount { get { return InvoiceLines.Sum(il => il.Amount); }}
public EntitySet<InvoiceLines> InvoiceLines {get;set;};
}
public class InvoiceLine
{
public Invoice Invoice {get;set;}
public int InvoiceID {get;set;}
public int Amount {get;set;}
public string SomeHugeString {get;set;}
}
(The real classes are sqlmetal generated, I shortened it down here to get to the point).
Querying for all amounts:
var amounts = from i in invoice select i.Amount;
This will cause all invoicelines to be lazy loaded with one database call per invoice. I can solve it with data load options, but that would cause the entire InvoiceLine objects to be read, including SomeHugeString.
Repeating the amount calculation in the query will get a good SQL translation:
var amounts = from i in invoice select i.InvoiceLines.Sum(il => il.Amount);
I sould like to have linq-to-sql somehow get part of the expression tree from a function/property. Is there a way to rewrite Invoice.Amount so that the first amounts query will give the same SQL translation as the second one?
You can do something similar using AsExpandable() from LINQKit:
Expression<Func<Invoice, int>> getAmount =
i => i.InvoiceLines.Sum(il => il.Amount);
var amounts = from i in invoice.AsExpandable() select getAmount.Invoke(i);
You can create your own functions using IQueryable interface.
I've used standard NorthWind DB:
public static class LinqExtensions
{
public static IQueryable<int> CalculateAmounts(this IQueryable<Order> order)
{
return from o in order select o.Order_Details.Sum(i => i.Quantity);
}
}
var amounts = (from o in context.Orders select o).CalculateAmounts();
This code generates such SQL:
SELECT [t2].[value]
FROM [dbo].[Orders] AS [t0]
OUTER APPLY (
SELECT SUM(CONVERT(Int,[t1].[Quantity])) AS [value]
FROM [dbo].[Order Details] AS [t1]
WHERE [t1].[OrderID] = [t0].[OrderID]
) AS [t2]
I'd suggest you set the 'SomeHugeString' property to be lazy loaded. This way you can load InvoiceLine without getting that huge string, which means you can use DataLoadOptions.LoadWith():
I'm mapping some POCO classes to an existing database with several lookup tables and I was wondering what would be the most efficient or recommended way to proceed.
Let's say there's an Employee model class that maps to an Employee table:
public class Employee
{
public int ID { get; set }
public string Name { get; set }
[ForeignKey("Role")]
public int RoleID { get; set }
public virtual EmployeeRole Role { get; set }
}
and the EmployeeRole class/table looks like:
public class EmployeeRole
{
public int ID { get; set } // For instance: 1
public string Description { get; set } // for instance: Manager
}
I know I don't need to explicitly declare the Foreign Key RoleID, but I actually have to perform quite an amount of queries that depend on an Employee having the EmployeeRole of "Manager".
Now the question is, what is better in terms of combining efficiency, code readability and "data independence"?
Assuming employees is an IQueryable just pulled from a repository:
employees.Where(e => e.RoleID == 1);
employees.Where(e => e.EmployeeRole.ID == 1);
employees.Where(e => e.EmployeeRole.Description == "Manager");
2 and 3 have the disadvantage of having to lazy-load the navigation property, but 1 has the disadvantage that RoleID == 1 is rather meaningless and bound to the current state of the database.
How should I proceed? Is there an option 4?
My guess is that, all 3 queries would yield similar results. At least 1 and 2 should produce the same SQL. You can see the generated SQL statement by calling "ToTraceString" on the ObjectSet instance (I would have to look this up, might be hidden somewhere else).
-- Edit
I've recreated your model and found out that the following queries were used (SQL Server). You have to cast the query to System.Data.Objects.ObjectQuery, which provides the ToTraceString() method.
employees.Where(e => e.RoleID == 1);
employees.Where(e => e.EmployeeRole.ID == 1);
// Both result in:
SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].[RoleId] AS [RoleId]
FROM [dbo].[Employees] AS [Extent1]
WHERE 1 = [Extent1].[RoleId]
and
employees.Where(e => e.EmployeeRole.Description == "Manager");
SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].[RoleId] AS [RoleId]
FROM [dbo].[Employees] AS [Extent1]
INNER JOIN [dbo].[Roles] AS [Extent2] ON [Extent1].[RoleId] = [Extent2].[Id]
WHERE N'Manager' = [Extent2].[Name]
A bit late, but I introduced the concept of "flags" in my tables with great success. For example if you know what you may often query for managers, add a bit to the table called
"Manager", and in your code simply do e.EmployeeRole.Manager and your done. Strongly typed and has real business meaning.