Entity Framework 6 TPH Inheritance Enum Field Generate Slow Query - performance

I use EF 6.0 code first and TPH inheritance in my project. I see that if i use enum field in my classes EF generates sql statement with casting. So my query runs very slowly and performance is down. How can i remove casting in my sql query?
Thanks.
var db = new AppContext();
var p = db.Products.FirstOrDefault(x => x.ProdId == 1);
public enum MyEnum
{
Field1 = 1,
Field2 = 2
}
public class Product
{
[Key]
public int ProdId { get; set; }
public string ProdName { get; set; }
//I run my code with this property and without this property.
//public MyEnum MyEnum { get; set; }
}
public class Chair : Product
{
public string ChairProp1 { get; set; }
}
public class Seat : Product
{
public string SeatProp1 { get; set; }
}
//EF generate this SQL without enum field. This is good SQL statements.
SELECT TOP (1)
[Extent1].[Discriminator] AS [Discriminator],
[Extent1].[ProdId] AS [ProdId],
[Extent1].[ProdName] AS [ProdName],
[Extent1].[ChairProp1] AS [ChairProp1],
[Extent1].[SeatProp1] AS [SeatProp1]
FROM [dbo].[Products] AS [Extent1]
WHERE ([Extent1].[Discriminator] IN (N'Chair',N'Seat',N'Product')) AND (1 = [Extent1].[ProdId])
//EF generate this SQL with enum field (myenum). there is alot of casting.i want to remove casting.
SELECT
[Limit1].[C1] AS [C1],
[Limit1].[ProdId] AS [ProdId],
[Limit1].[ProdName] AS [ProdName],
[Limit1].[MyEnum] AS [MyEnum],
[Limit1].[C2] AS [C2],
[Limit1].[C3] AS [C3]
FROM ( SELECT TOP (1)
[Extent1].[ProdId] AS [ProdId],
[Extent1].[ProdName] AS [ProdName],
[Extent1].[MyEnum] AS [MyEnum],
CASE WHEN ([Extent1].[Discriminator] = N'Product') THEN '0X' WHEN ([Extent1].[Discriminator] = N'Chair') THEN '0X0X' ELSE '0X1X' END AS [C1],
CASE WHEN ([Extent1].[Discriminator] = N'Product') THEN CAST(NULL AS varchar(1)) WHEN ([Extent1].[Discriminator] = N'Chair') THEN [Extent1].[ChairProp1] END AS [C2],
CASE WHEN ([Extent1].[Discriminator] = N'Product') THEN CAST(NULL AS varchar(1)) WHEN ([Extent1].[Discriminator] = N'Chair') THEN CAST(NULL AS varchar(1)) ELSE [Extent1].[SeatProp1] END AS [C3]
FROM [dbo].[Products] AS [Extent1]
WHERE ([Extent1].[Discriminator] IN (N'Chair',N'Seat',N'Product')) AND (1 = [Extent1].[ProdId])
) AS [Limit1]

It seems you missed the configuration of discriminator . You need to specify which field is the discriminator and what's the type of that ,
You can do it by fluent api like this :
public class ChairConfig : EntityTypeConfiguration<Chair>
{
public ChairConfig()
{
this.Map(m => m.Requires(discriminator: "MyEnum").HasValue(MyEnum.Field1));
}
}
public class SeatConfig : EntityTypeConfiguration<Seat>
{
public SeatConfig()
{
this.Map(m => m.Requires(discriminator: "MyEnum").HasValue(MyEnum.Field2));
}
}
And in query you need to specify which type you wanna retrieve like this :
var db = new AppContext();
var p = db.Products.OfType<Chair>().FirstOrDefault(x => x.ProdId == 1);

Related

Linq query to select any in list against a list

Using EF Core code-first, and I want to find any record with a similar list of a foreign entities to the entity I already have.
public class ClownModel {
public int Id { get; set; }
public List<CarModel> Cars { get; set; }
}
public class CarModel {
public int Id { get; set; }
}
var MyClown = new ClownModel() { /*add properties*/ }
//or maybe an existing record selected from database, just some ClownModel instance
Basically, "Select all the ClownModels where they have any Cars.Id that are in my MyClown.Cars"
Assuming that ClownModel has unique CarModel Id's, you can use the following query:
Matches All Ids
var ids = MyClown.Cars.Select(c => c.Id).ToList();
var query =
from cm in ctx.ClownModel
where cm.Cars.Where(c => ids.Contains(c.Id)).Count() == ids.Count
select cm;
Matches Any Ids
var ids = MyClown.Cars.Select(c => c.Id).ToList();
var query =
from cm in ctx.ClownModel
where cm.Cars.Where(c => ids.Contains(c.Id)).Any()
select cm;

Adding where and sum to lambda expression

I have the below Linq query:
var qry = from Output in db.Outputs
join ShiftHours in db.ShiftHourses on Output.ShiftHour equals ShiftHours.ShiftHour
join ShiftData in db.ShiftDatas on Output.ShiftID equals ShiftData.ShiftID
where ShiftData.ShiftDate == date && ShiftData.Line == line
select new ProgressData()
{
CPM = ShiftData.CPM,
Target = ShiftData.Target,
CurrentOutput = db.Outputs.Sum(x=>x.Quantity),
PercentOfTarget = (db.Outputs.Sum(x=>x.Quantity) / ShiftData.Target) * 100
};
It is almost doing what I want but as it stands, the CurrentOutput lambda expression is returning the sum of the entire Quantity column of the Output table as I am unsure how to add in a 'Where' clause as well as the sum function (and hence the PercentOfTarget is also incorrect).
The where clause needs to be the same as the first where clause (date and line are parameters passed to the method):
where ShiftData.ShiftDate == date && ShiftData.Line == line
Can anyone help?
EDIT: Clarification of CurrentOutput.
In the 'Output' table there can be multiple records for a given 'ShiftData.ShiftDate' and 'ShiftData.Line' combination so I would like to calculate a sum of the 'Output' table 'Quantity' column values for a specified 'ShiftDate' and 'Line'
EDIT: Further clarification
This is some sample data from the Output table (OutputID is an auto-increment PK):
public class Output
{
[Key]
public int OutputId { get; set; }
public int ShiftID { get; set; }
public int Quantity { get; set; }
public int ShiftHour { get; set; }
public virtual ShiftData ShiftData { get; set; }
}
This is some sample data from the ShiftData table (ShiftID is an auto-increment PK, there will be more than one record for each date as further line numbers are added):
public class ShiftData
{
[Key]
public int ShiftID { get; set; }
public DateTime ShiftDate { get; set; }
public string Line { get; set; }
public int CPM { get; set; }
public double Target { get; set; }
}
So using the above sample data, I am trying to populate a ProgressData object:
public class ProgressData
{
public int CPM { get; set; }
public double Target { get; set; }
public int CurrentOutput { get; set; }
public double PercentOfTarget { get; set; }
}
Based on the sample data, I would expect my ProgressData object created for line 1 on 13/2/2014 to be populated as such:
CPM = 5, Target = 200, CurrentOutput = 120, PercentOfTarget = 60
You can try to do group join for that purpose :
var qry = from ShiftData in db.ShiftDatas
join Output in db.Outputs on ShiftData.ShiftID equals Output.ShiftID
into ShiftGroup
where ShiftData.ShiftDate == date && ShiftData.Line == line
select new ProgressData()
{
CPM = ShiftData.CPM,
Target = ShiftData.Target,
CurrentOutput = ShiftGroup.Sum(x=>x.Quantity),
PercentOfTarget = (ShiftGroup.Sum(x=>x.Quantity) / ShiftData.Target) * 100
};
Another thing, I can't see why you need to do join with ShiftHours here, since none of it's property used in select statement.
Just as #har07 posted I managed to get it working using the below. I am posting this for reference as it does answer the original question but I'm going to try and use #har07's code as it's tidier than mine.
var qry = (from Output in db.Outputs
join ShiftHours in db.ShiftHourses on Output.ShiftHour equals ShiftHours.ShiftHour
join ShiftData in db.ShiftDatas on Output.ShiftID equals ShiftData.ShiftID
where ShiftData.ShiftDate == date && ShiftData.Line == line
select new
{
ShiftData.ShiftDate,
ShiftData.Line,
ShiftData.CPM,
ShiftData.Target,
Output.Quantity
}).ToList();
var progress = qry.GroupBy(l => l.ShiftDate).Select(g => new ProgressData()
{
CPM = g.Where(c => c.ShiftDate == date && c.Line == line).Select(c => c.CPM).FirstOrDefault(),
Target = g.Where(c => c.ShiftDate == date && c.Line == line).Select(c => c.Target).FirstOrDefault(),
CurrentOutput = g.Where(c => c.ShiftDate == date && c.Line == line).Sum(c => c.Quantity),
PercentOfTarget = g.Where(c => c.ShiftDate == date && c.Line == line).Sum(c => (c.Quantity / c.Target) * 100)
});
return progress.FirstOrDefault();

How can I use be generic to result of linq query?

Be first, my English is not very good. So I am sorry about that. :)
My question as the title. I have two tables on my database:
Siniflar (SinifId, SinifAdi, Kapasite, OgretmenId)
Ogretmenler (OgretmenId, Ad, Soyad, Brans)
Query:
var siniflar = (from s in db.Siniflar
join o in db.Ogretmenler
on s.OgretmenId equals o.OgretmenId
select new { s.SinifId, s.SinifAdi, s.Kapasite, o.Ad }).ToList();
I want to use the result of the query as a generic, like this:
public List<Siniflar> SiniflariListele()
{
var siniflar = (from s in db.Siniflar
join o in db.Ogretmenler
on s.OgretmenId equals o.OgretmenId
select new { s.SinifId, s.SinifAdi, s.Kapasite, o.Ad }).ToList();
return siniflar;
}
But I get an error. Because the result is anonymous types.
Error: Cannot implicitly convert type System.Collections.Generic.List<AnonymousType#1> to System.Collections.Generic.List<Entity.Siniflar>
How Can I use be generic result. What Can I do for that?
I changed question:
Class:
public class RSiniflar
{
public int SinifId { get; set; }
public string SinifAdi { get; set; }
public int Kapasite { get; set; }
public string OgretmenAdiSoyadi { get; set; }
}
Metod:
public List<RSiniflar> SiniflariListele()
{
List<RSiniflar> siniflar = (from s in db.Siniflar
join o in db.Ogretmenler
on s.OgretmenId equals o.OgretmenId
select new RSiniflar
{
SinifId = s.SinifId,
SinifAdi = s.SinifAdi,
Kapasite = s.Kapasite,
OgretmenAdiSoyadi = o.Ad + ' ' + o.Soyad
}).ToList();
return siniflar;
}
But now error to query: Unable to create a constant value of type 'System.Object'. Only primitive types or enumeration types are supported in this context.
I think, there are a problem on my query... I get an error same when query to var type...
You can return typed list, but you can't return a list of anonymous type. So you need to create a class which will handle your results:
public class A
{
public int SinifId { get; set; }
public int SiniAdi { get; set; }
public int Kapasite { get; set; }
public int Ad { get; set; }
}
And then use it in your query, instead of anonymous objects:
public List<A> SiniflariListele()
{
var siniflar = (from s in db.Siniflar
join o in db.Ogretmenler
on s.OgretmenId equals o.OgretmenId
select new A {
SinifId = s.SinifId,
SinifAdi = s.SinifAdi,
Kapasite = s.Kapasite,
Ad = o.Ad
}).ToList();
return siniflar;
}
You could do the same with an existing class (e.g. Siniflar), but you have to be sure that this class has all necessary properties.
Rather than using new { property1, property2 }, create new Siniflar's in your select.
E.g.
select new Siniflar() {
SinifId = s.SinifId,
SinifAdi = s.SinifAdi,
Kapasite = s.Kapasite }).ToList()
Note: as lazyberezovsky mentioned, your mapping seems to be flawed, as Siniflar does not contain the property Ad.

ASP.Net MVC converting Sql to Linq

I'm updating an old app, to use EF and Linq. I'm having trouble with one of the queries - in SQL it is:
SELECT id, type_id, rule_name, rule_start, rule_end, rule_min
FROM Rules
WHERE (rule_min > 0)
AND (rule_active = 1)
AND (rule_fri = 1)
AND ('2012-01-01' BETWEEN rule_start AND rule_end)
AND (id IN
(SELECT rule_id
FROM RulesApply
WHERE (type_id = 3059)))
ORDER BY pri
So far I have:
var rules = db.Rules.Include("RulesApply")
.Where(t => (t.rule_active == 1)
&& (t.rule_min > 0)
&& (dteFrom >= t.rule_start && dteFrom <= t.rule_end)
&& (this is where I'm stuck)
)
.OrderBy(r => r.pri);
It's the last subquery I'm stuck with adding into the LINQ above:
AND (id IN
(SELECT rule_id
FROM RulesApply
WHERE (type_id = 3059)))
Models are:
public class Rule
{
[Key]
public Int64 id { get; set; }
public Int64 hotel_id { get; set; }
public byte rule_active { get; set; }
public DateTime rule_start { get; set; }
public DateTime rule_end { get; set; }
public int rule_min { get; set; }
public int pri { get; set; }
public virtual ICollection<RuleApply> RulesApply { get; set; }
}
public class RuleApply
{
[Key, Column(Order = 0)]
public Int64 type_id { get; set; }
[Key, Column(Order = 1)]
public Int64 rule_id { get; set; }
[ForeignKey("rule_id")]
public virtual Rule Rule { get; set; }
}
Can anyone please help me complete this query?
Thank you,
Mark
Try doing this:
var rules = db.Rules.Include("RulesApply")
.Where(t => (t.rule_active == 1)
&& (t.rule_min > 0)
&& (dteFrom >= t.rule_start && dteFrom <= t.rule_end)
&& t.RulesApply.Any(a => a.type_id == 3059)
.OrderBy(r => r.pri);
If t.RulesApply is illegal (i.e. doesn't compile), then replace it with the correct reference to the navigation property found on your Rules object that points to the RulesApply object.
If you have set up navigational properties between the entities, you can navigate from one to the other:
//This gets the RulesApply object
var rulesapply = db.RulesApply.Single(x=> x.type_id == 3059);
//This gets all Rules connected to the rulesapply object through its navigational property
var rules = rulesapply.Rules;
//You can use LINQ to further refine what you want
rules = rules.Where( x=> /* and so on...*/ );
You can stack these statements together on a single line, I only split them up for readability purposes :)

How to write a properly joined query with method based syntax in Entity SQL?

I am working on a larger system with Entity Framework. Personally, I do like the method based syntax for writing LINQ / ESQL queries.
But I cannnot figure out, how the following joined query can be written properly.
Suppose, i have the following Entity classes and DbContext repository:
public class A
{
[Key]
public int ID{get;set;}
public virtual ICollection<B> BCollection { get; set; }
}
public class B
{
[Key]
public int ID { get; set; }
public virtual A A { get; set; }
public virtual ICollection<C> CCollection { get; set; }
public virtual ICollection<D> DCollection { get; set; }
}
public class C
{
[Key]
public int ID { get; set; }
public virtual B B { get; set; }
}
public class D
{
[Key]
public int ID { get; set; }
public virtual B B { get; set; }
}
public class Entities : DbContext
{
public DbSet<A> SetA { get; set; }
public DbSet<B> SetB { get; set; }
public DbSet<C> SetC { get; set; }
public DbSet<D> SetD { get; set; }
}
Now I want to get the "whole" tree: All items of class A, with their attached items of class B, where each item of class B is equipped with items of class C and D.
I can do that with this query:
var x = db.SetA
.Include(a => a.BCollection.Select(b => b.CCollection))
.Include(a => a.BCollection.Select(b => b.DCollection))
.ToList();
But i think this can be done easier, right?
Please note: I DO want to eager load the whole tree, as I DO NOT want to use lazy loading.
Edit:
This is not a question regarding one single problem, my question is more about the approach. When I write my queries like shown above, EF generates one hell of a giant query:
SELECT [Project3].[ID] AS [ID],
[Project3].[C9] AS [C1],
[Project3].[C2] AS [C2],
[Project3].[C3] AS [C3],
[Project3].[C4] AS [C4],
[Project3].[C1] AS [C5],
[Project3].[C5] AS [C6],
[Project3].[C6] AS [C7],
[Project3].[C7] AS [C8],
[Project3].[C8] AS [C9]
FROM (SELECT [Extent1].[ID] AS [ID],
[UnionAll1].[C1] AS [C1],
[UnionAll1].[ID] AS [C2],
[UnionAll1].[ID1] AS [C3],
[UnionAll1].[A_ID] AS [C4],
[UnionAll1].[ID2] AS [C5],
[UnionAll1].[B_ID] AS [C6],
[UnionAll1].[C2] AS [C7],
[UnionAll1].[C3] AS [C8],
CASE
WHEN ([UnionAll1].[ID] IS NULL) THEN CAST(NULL AS int)
ELSE 1
END AS [C9]
FROM [dbo].[A] AS [Extent1]
OUTER APPLY (SELECT CASE
WHEN ([Extent3].[ID] IS NULL) THEN CAST(NULL AS int)
ELSE 1
END AS [C1],
[Extent2].[ID] AS [ID],
[Extent2].[ID] AS [ID1],
[Extent2].[A_ID] AS [A_ID],
[Extent3].[ID] AS [ID2],
[Extent3].[B_ID] AS [B_ID],
CAST(NULL AS int) AS [C2],
CAST(NULL AS int) AS [C3]
FROM [dbo].[B] AS [Extent2]
LEFT OUTER JOIN [dbo].[C] AS [Extent3]
ON [Extent2].[ID] = [Extent3].[B_ID]
WHERE [Extent1].[ID] = [Extent2].[A_ID]
UNION ALL
SELECT 2 AS [C1],
[Extent4].[ID] AS [ID],
[Extent4].[ID] AS [ID1],
[Extent4].[A_ID] AS [A_ID],
CAST(NULL AS int) AS [C2],
CAST(NULL AS int) AS [C3],
[Extent5].[ID] AS [ID2],
[Extent5].[B_ID] AS [B_ID]
FROM [dbo].[B] AS [Extent4]
INNER JOIN [dbo].[D] AS [Extent5]
ON [Extent4].[ID] = [Extent5].[B_ID]
WHERE [Extent1].[ID] = [Extent4].[A_ID]) AS [UnionAll1]) AS [Project3]
ORDER BY [Project3].[ID] ASC,
[Project3].[C9] ASC,
[Project3].[C3] ASC,
[Project3].[C1] ASC
But what I actually would write myself is something like:
SELECT *
FROM D RIGHT OUTER JOIN
B ON D.B_ID = B.ID LEFT OUTER JOIN
C ON B.ID = C.B_ID RIGHT OUTER JOIN
A ON B.A_ID = A.ID
I hope this helps to clarify my question. The point is: How can I make Entity Framework generate more efficient queries?
I think following LINQ would generate a "mega" query you are looking for:
var query =
db.SetA
.Join(db.SetB, a => a, b => b.A, (a, b) => new
{
A = a,
B = b
})
.Join(db.SetC, ab => ab.B, c => c.B, (ab, c) => new
{
A = ab.A,
B = ab.B,
C = c
})
.Join(db.SetD, abc => abc.B, d => d.B, (abc, d) => new
{
A = abc.A,
B = abc.B,
C = abc.C,
D = d
});
foreach (var abcd in query)
{
Console.WriteLine("{0}-{1}-{2}-{3}", abcd.A.ID, abcd.B.ID, abcd.C.ID, abcd.D.ID);
}

Resources