EF Core - translating to SQL simple select - c#

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.

Related

EF Core behaves differently when DbSet type parameter is changed

I'm using EF Core 5.0.5
I have a database with two tables "t_ErrorLogs_Ciliate_IN" and "t_ErrorLogs_Ciliate_OUT" they have the same columns. One process writes to one of the tables and another to the other but the records are structured in the exact same way, which is why they have identical columns.
My DbContext looks like this:
public class CiliateLoggingContext : DbContext
{
public CiliateLoggingContext() { }
public CiliateLoggingContext(DbContextOptions options) : base(options) { }
public virtual DbSet<ErrorLog_Ciliate_IN> t_ErrorLogs_Ciliate_IN { get; set; }
public virtual DbSet<ErrorLog_Ciliate_OUT> t_ErrorLogs_Ciliate_OUT { get; set; }
}
The classes "ErrorLog_Ciliate_IN" and "ErrorLog_Ciliate_OUT" are just:
public partial class ErrorLog_Ciliate_IN : CiliateErrorLogBase { }
public partial class ErrorLog_Ciliate_OUT : CiliateErrorLogBase { }
public class CiliateErrorLogBase
{
// properties representing the columns.
}
If I try to select logs like this:
Task<List<ErrorLog_Ciliate_IN>> SelectMethod()
{
context.t_ErrorLogs_Ciliate_IN.Where(log => log.Id > 10).OrderByDescending(log => log.Id).Take(20).ToListAsync();
}
Everything is fine, I get a task, I await it and it gives me a list of ErrorLogs_Ciliate_IN objects.
Super!
However, since the classes "ErrorLog_Ciliate_IN" and "ErrorLog_Ciliate_OUT" are exactly the same I wanted to make a single "ErrorLog_Ciliate" class and use that.
public partial class ErrorLog_Ciliate : CiliateErrorLogBase { }
and change the DbContext to:
public class CiliateLoggingContext : DbContext
{
public CiliateLoggingContext() { }
public CiliateLoggingContext(DbContextOptions options) : base(options) { }
public virtual DbSet<ErrorLog_Ciliate> t_ErrorLogs_Ciliate_IN { get; set; }
public virtual DbSet<ErrorLog_Ciliate> t_ErrorLogs_Ciliate_OUT { get; set; }
}
However EF suddenly decides to select from "ErrorLog_Ciliate", instead of "t_ErrorLogs_Ciliate_IN".
This is what "ToQueryString()" returns with the first version of the DbContext - the one that has a separate class for each table:
Methods:
string SelectIN()
{
context.t_ErrorLogs_Ciliate_IN.Where(log => log.Id > 10).OrderByDescending(log => log.Id).Take(20).ToListAsync();
}
string SelectOUT()
{
context.t_ErrorLogs_Ciliate_OUT.Where(log => log.Id > 10).OrderByDescending(log => log.Id).Take(20).ToListAsync();
}
The results:
DECLARE #__p_0 int = 20;
SELECT TOP(#__p_0) [t].[Id], [t].[Date], [t].[Type]
FROM [t_ErrorLogs_Ciliate_IN] AS [t]
WHERE [t].[Id] > 10
ORDER BY [t].[Id] DESC
DECLARE #__p_0 int = 20;
SELECT TOP(#__p_0) [t].[Id], [t].[Date], [t].[Type]
FROM [t_ErrorLogs_Ciliate_OUT] AS [t]
WHERE [t].[Id] > 10
ORDER BY [t].[Id] DESC
and if I use the second version of the DbContext, where both DbSets use the same type parameter of ErrorLog_Ciliate the weirdest thing happens:
DECLARE #__p_0 int = 20;
SELECT TOP(#__p_0) [a].[Id], [a].[Data], [a].[Type]
FROM [CiliateErrorLog] AS [a]
WHERE [a].[Id] > 10
ORDER BY [a].[Id] DESC
DECLARE #__p_0 int = 20;
SELECT TOP(#__p_0) [a].[Id], [a].[Data], [a].[Type]
FROM [CiliateErrorLog] AS [a]
WHERE [a].[Id] > 10
ORDER BY [a].[Id] DESC
Notice how now it tries to select from the name of the class, where before it tried to select from the name of the DbContext parameter.
If I use a hybrid version of the DbContext, one that has:
public virtual DbSet<ErrorLog_Ciliate> t_ErrorLogs_Ciliate_IN { get; set; }
public virtual DbSet<ErrorLog_Ciliate_OUT> t_ErrorLogs_Ciliate_OUT { get; set; }
I get the normal first results of:
DECLARE #__p_0 int = 20;
SELECT TOP(#__p_0) [t].[Id], [t].[Date], [t].[Type]
FROM [t_ErrorLogs_Ciliate_IN] AS [t]
WHERE [t].[Id] > 10
ORDER BY [t].[Id] DESC
DECLARE #__p_0 int = 20;
SELECT TOP(#__p_0) [t].[Id], [t].[Date], [t].[Type]
FROM [t_ErrorLogs_Ciliate_OUT] AS [t]
WHERE [t].[Id] > 10
ORDER BY [t].[Id] DESC
Why is this happening and how can I use the same type parameter for two properties and still have EF recognize it should select "from" the property names?
EF supports only one mapping in the model per entity type per DbContext, so you can't have two instances of the same type mapped to different tables in the same context. If you check out the EF Fluent API you will see that it operates on basis of types, not DbSet's. So to share table structure between different tables, as you already done yourself, you need to use inheritance - most common approach is to inherit entity types from the same base class, or more esoteric one - inherit DbContext's overriding entity setup in OnModelCreating.

ServiceStack LoadReferences when using SQL Query

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

Querying an auto-generated EF junction table

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()

Break out part of linq-to-sql expression to a separate function

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():

Entity framework generated SQL problem (multiple joins on same table)

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

Categories

Resources