This is how our Linq's query should be like in SQL:
select p.BusinessEntityID, p.FirstName, TheCount = count(ph.BusinessEntityId) from Person.Person p left join HumanResources.EmployeePayHistory ph on p.BusinessEntityID = ph.BusinessEntityID group by p.BusinessEntityID, p.FirstName having count(ph.BusinessEntityId) = 0 or count(ph.BusinessEntityId) = 3 order by TheCount
The two domain entities however doesn't belong in same aggregate..
namespace Domain
{
public static class ThePerson
{
public class Person
{
public int BusinessEntityId { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
// public virtual ICollection<TheHumanResources.EmployeePayHistory> PayHistories { get; set; }
}
}
}
namespace Domain
{
public static class TheHumanResources
{
public class EmployeePayHistory
{
public int BusinessEntityId { get; set; }
public DateTime RateChangeDate { get; set; }
public virtual ThePerson.Person Person { get; set; }
public decimal Rate { get; set; }
}
}
}
namespace DomainMapping
{
public class TheDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ThePerson.Person>().ToTable("Person.Person")
.HasKey(x => x.BusinessEntityId);
// .HasMany(x => x.PayHistories).WithRequired().HasForeignKey(x => x.BusinessEntityId);
modelBuilder.Entity<TheHumanResources.EmployeePayHistory>()
.ToTable("HumanResources.EmployeePayHistory")
.HasKey(x => new { x.BusinessEntityId, x.RateChangeDate } )
.HasRequired(x => x.Person)
.WithRequiredDependent()
.Map(m => m.MapKey("BusinessEntityID"));
}
}
}
..hence we can't make a simpler query like this:
var query =
from person in ctx.Set<ThePerson.Person>().SelectMany(x => x.PayHistories.DefaultIfEmpty(), (person, payHistory) => new { person, payHistory })
group person by new { person.person.BusinessEntityId, person.person.FirstName } into persons
let TheCount = persons.Sum(x => x.payHistory != null ? 1 : 0)
where TheCount == 0 || TheCount == 3
orderby TheCount
select new { persons.Key.BusinessEntityId, persons.Key.FirstName, TheCount };
Output:
SELECT
[GroupBy1].[K1] AS [BusinessEntityId],
[GroupBy1].[K2] AS [FirstName],
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
[Join1].[K1] AS [K1],
[Join1].[K2] AS [K2],
SUM([Join1].[A1]) AS [A1]
FROM ( SELECT
[Extent1].[BusinessEntityId] AS [K1],
[Extent1].[FirstName] AS [K2],
CASE WHEN ( NOT (([Extent2].[BusinessEntityId] IS NULL) AND ([Extent2].[RateChangeDate] IS NULL))) THEN 1 ELSE 0 END AS [A1]
FROM [Person].[Person] AS [Extent1]
LEFT OUTER JOIN [HumanResources].[EmployeePayHistory] AS [Extent2] ON [Extent1].[BusinessEntityId] = [Extent2].[Person_BusinessEntityId1]
) AS [Join1]
GROUP BY [K1], [K2]
) AS [GroupBy1]
WHERE [GroupBy1].[A1] IN (0,3)
ORDER BY [GroupBy1].[A1] ASC
Following is easy to read Linq with left join group by and having, but it's not efficient, this creates a subquery in SELECT:
var query = from person in ctx.Set<ThePerson.Person>()
join payHistory in ctx.Set<TheHumanResources.EmployeePayHistory>() on person.BusinessEntityId equals payHistory.BusinessEntityId into payHistories
from payHistoryNullable in payHistories.DefaultIfEmpty()
let TheCount = payHistories.Count()
where TheCount == 0 || TheCount == 3
orderby TheCount ascending
select new { person.BusinessEntityId, person.FirstName, TheCount };
Output:
SELECT
[Project1].[BusinessEntityId] AS [BusinessEntityId],
[Project1].[FirstName] AS [FirstName],
[Project1].[C1] AS [C1]
FROM ( SELECT
[Extent1].[BusinessEntityId] AS [BusinessEntityId],
[Extent1].[FirstName] AS [FirstName],
-- subquery
(SELECT
COUNT(1) AS [A1]
FROM [HumanResources].[EmployeePayHistory] AS [Extent3]
WHERE [Extent1].[BusinessEntityId] = [Extent3].[BusinessEntityId]) AS [C1]
FROM [Person].[Person] AS [Extent1]
LEFT OUTER JOIN [HumanResources].[EmployeePayHistory] AS [Extent2] ON [Extent1].[BusinessEntityId] = [Extent2].[BusinessEntityId]
) AS [Project1]
WHERE [Project1].[C1] IN (0,3)
ORDER BY [Project1].[C1] ASC
Not as readable as the Linq query above, but this is better as this uses GROUP BY:
var query = from person in ctx.Set<ThePerson.Person>()
join payHistory in ctx.Set<TheHumanResources.EmployeePayHistory>() on person.BusinessEntityId equals payHistory.BusinessEntityId into payHistories
from payHistoryNullable in payHistories.DefaultIfEmpty()
group payHistoryNullable by new { person.BusinessEntityId, person.FirstName } into payHistoriesNullable
let TheCount = payHistoriesNullable.Sum(eachPayHistoryNullable => eachPayHistoryNullable != null ? 1 : 0)
where TheCount == 0 || TheCount == 3
orderby TheCount ascending
select new { payHistoriesNullable.Key.BusinessEntityId, payHistoriesNullable.Key.FirstName, TheCount };
Output:
SELECT
[GroupBy1].[K1] AS [BusinessEntityId],
[GroupBy1].[K2] AS [FirstName],
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
[Join1].[K1] AS [K1],
[Join1].[K2] AS [K2],
SUM([Join1].[A1]) AS [A1]
FROM ( SELECT
[Extent1].[BusinessEntityId] AS [K1],
[Extent1].[FirstName] AS [K2],
CASE WHEN ( NOT (([Extent2].[BusinessEntityId] IS NULL) AND ([Extent2].[RateChangeDate] IS NULL))) THEN 1 ELSE 0 END AS [A1]
FROM [Person].[Person] AS [Extent1]
LEFT OUTER JOIN [HumanResources].[EmployeePayHistory] AS [Extent2] ON [Extent1].[BusinessEntityId] = [Extent2].[BusinessEntityId]
) AS [Join1]
-- efficient, uses GROUP BY
GROUP BY [K1], [K2]
) AS [GroupBy1]
WHERE [GroupBy1].[A1] IN (0,3)
ORDER BY [GroupBy1].[A1] ASC
Happy Coding!
No comments:
Post a Comment