Understanding EF core subquery [duplicate] - c#

This question already has answers here:
Multiple aggregate functions in one query using Linq to SQL [duplicate]
(1 answer)
Return multiple aggregate columns in LINQ
(4 answers)
Closed last year.
I have the following linq statements which I am hoping to cut down from multiple round trips to my database to one using subquery.
This is my code before refactoring:
IQueryable<Customers> customers= this.BaseQuery.Where(o => o.EmailAddress == emailAddress);
int totalCustomersCount = await customers.CountAsync();
int totalDisabledCustomersCount = await customers.Where(o => CustomerState.Disabled == o.CurrentState).CountAsync();
int totalCancelledCustomersCount = await customers.Where(o => CustomerState.Cancelled == o.CurrentState).CountAsync();
int totalExpiredCustomersCount = await customers.Where(o => CustomerState.Archived == o.CurrentState).CountAsync();
int totalPremiumCustomerCount = await customers.Where(o => CustomerState.Premium== o.CurrentState).CountAsync();
CustomerStatistics customerStatistics = new CustomerStatistics()
{
TotalCustomersCount = totalCustomersCount,
TotalDisabledCustomersCount = totalDisabledCustomersCount,
TotalCancelledCustomersCount = totalCancelledCustomersCount,
TotalExpiredCustomersCount= totalExpiredCustomersCount,
TotalPremiumCustomerCount= totalPremiumCustomerCount,
};
and I have reworked this code to look like this
IQueryable<Customers>? query = this.Context.Customers.Where(o => o.EmailAddress == emailAddress);
IQueryable<CustomerStatistics >? myQuery= query.Select(o => new CustomerStatistics()
{
TotalCustomersCount = query.Count(),
TotalDisabledCustomersCount = query.Where(o => o.CurrentState == CustomerState.Disabled).Count(),
TotalCancelledCustomersCount = query.Where(o => o.CurrentState == CustomerState.Cancelled).Count(),
TotalExpiredCustomersCount= query.Where(o => o.CurrentState == CustomerState.Archived).Count(),
TotalPremiumCustomerCount= query.Where(o => o.CurrentState == CustomerState.Premium).Count(),
});
Is this along the right lines? To me, it still looks like it will be makign round trips to the database to get each count.
Update to the example query:
IQueryable<Order> orders = this.BaseQuery.Where(o => o.CustomerContactId == customerContactId);
int totalOrderCount = await orders.CountAsync();
int totalInProgressOrderCount = await orders.Where(o => OrderState.Fulfilment == o.CurrentState).CountAsync();
int totalCancelledOrderCount = await orders.Where(o => OrderState.Cancelled == o.CurrentState).CountAsync();
int totalExpiredOrderCount = await orders.Where(o => OrderState.Archived == o.CurrentState).CountAsync();
int totalQuoteCount = await orders.Where(o => OrderState.Initial == o.CurrentState || OrderState.QuoteIssued == o.CurrentState || OrderState.ReadyToPlace == o.CurrentState || OrderState.CheckGeometry == o.CurrentState).CountAsync();
int totalIssuedOrderCount = await orders.Where(o => OrderState.QuoteIssued == o.CurrentState).CountAsync();
CustomerContactOrderStatistics customerContactOrderStatistics = new CustomerContactOrderStatistics()
{
TotalOrderCount = totalOrderCount,
TotalInProgressOrderCount = totalInProgressOrderCount,
TotalCancelledOrderCount = totalCancelledOrderCount,
TotalExpiredOrderCount = totalExpiredOrderCount,
TotalQuoteCount = totalQuoteCount,
TotalIssuedOrderCount = totalIssuedOrderCount,
};

You can do grouping by constant. It is special case which is handled by EF to make aggregation query:
vra query = this.Context.Customers
.Where(o => o.EmailAddress == emailAddress);
var statistics = = query.GroupBy(x => 1)
.Select(g => new CustomerStatistics()
{
TotalCustomersCount = g.Count(),
TotalDisabledCustomersCount = g.Count(o => o.CurrentState == CustomerState.Disabled),
TotalCancelledCustomersCount = g.Count(o => o.CurrentState == CustomerState.Cancelled),
TotalExpiredCustomersCount = g.Count(o => o.CurrentState == CustomerState.Archived),
TotalPremiumCustomerCount = g.Count(o => o.CurrentState == CustomerState.Premium),
});
For EF Core versions which do not support conditional Count, it can be emulated by Sum
var statistics = = query.GroupBy(x => 1)
.Select(g => new CustomerStatistics()
{
TotalCustomersCount = g.Count(),
TotalDisabledCustomersCount = g.Sum(o => o.CurrentState == CustomerState.Disabled ? 1 : 0),
TotalCancelledCustomersCount = g.Sum(o => o.CurrentState == CustomerState.Cancelled ? 1 : 0),
TotalExpiredCustomersCount= g.Sum(o => o.CurrentState == CustomerState.Archived ? 1 : 0),
TotalPremiumCustomerCount= g.Sum(o => o.CurrentState == CustomerState.Premium ? 1 : 0),
});

Related

Linq variable inside select

I have a following query
return _context.Table1
.Where(x => x.ContactId == contactKey.Id)
.Include(x => x.Table2)
.Include(x => x.Table3.Table4)
.Select(a =>
new MyReadModel
{
PriorityAssignment = true,
LastContactedDate = (a.Table3.Table4 != null && a.Table3.Table4.FirstOrDefault(h =>
h.Id == a.Table2.FkId
) != null ?
a.Table3.Table4.FirstOrDefault(h => && h.Id == a.Table2.FkId
).LastContactedDatetime : default
)
}
)
.ToListAsync();
What i wants is to simplify LastContactedDate assignment with in select. I think we can assign
a.Table3.Table4.FirstOrDefault(h =>
h.Id == a.Table2.FkId
)
to some variable but can't able to do it
can someone identify what is needed
With EF Core you don't have to check for null, LINQ Translator do not execute your code, but uses it for translation to the SQL. Also Includes is not needed if you do not retrieve whole objects.
return await _context.Table1
.Where(x => x.ContactId == contactKey.Id)
.Select(a => new MyReadModel
{
PriorityAssignment = true,
LastContactedDate = (DateTime?)a.Table3.Table4.Where(h => h.Id == a.Table2.FkId)
.OrderByDescending(h => LastContactedDatetime)
.Select(h => LastContactedDatetime)
.FirstOrDefault()
}).ToListAsync();
you can use it like this example
List<string> someList= new List<string>();
someList= someList.Select(x =>
{
var newVariable= "newVariable";
return newVariable;
}).ToList();
in your case
return _context.Table1
.Where(x => x.ContactId == contactKey.Id)
.Include(x => x.Table2)
.Include(x => x.Table3.Table4)
.Select(a => {
Table4 newVariable = null;
if(a.Table3.Table4 != null)
newVariable = a.Table3.Table4.FirstOrDefault(h => h.Id == a.Table2.FkId;
var result = new MyReadModel
{
PriorityAssignment = true,
LastContactedDate = (newVariable != null ? newVariable.LastContactedDatetime : default
)
};
}
) .ToListAsync();

c# Lambda referencing class object in Select method

I've written this code which works:
var uniqueCustomerIdList = services
.GroupBy(x => x.CustomerId)
.Select(cl => new Customer
{
CustomerId = cl.First().CustomerId,
CustomerName = cl.First().CompanyName,
PdfServices = services.Where(x => x.CustomerId == cl.First().CustomerId).ToList(),
PdfServiceLines = services.Where(x => x.CustomerId == cl.First().CustomerId).ToList()
.GroupBy(l => l.ServiceDescription)
.Select(cy => new PdfServiceLine
{
ServiceName = cy.First().ServiceDescription,
Quantity = cy.Count(),
UnitPrice = cy.First().PlanCharge,
ServiceCharges = services.Where(x => x.CustomerId == cl.First().CustomerId && x.ServiceDescription == cy.First().ServiceDescription).Sum(y => y.TotalBill),
UsageCharges = usage.Where(x => x.CustomerId == cl.First().CustomerId && x.ServiceDescription == cy.First().ServiceDescription).Sum(y => y.Charge),
Total = cy.Sum(c => c.PlanCharge),
}).ToList(),
PdfUsages = usage.Where(x => x.CustomerId == cl.First().CustomerId).ToList()
})
.ToList();
I wanted to know if it's possible to reference values from the outer Select statement in the inner statement? As it looks rather clunky at the moment.
For instance in the outer Customer select I use PdfServices - can I use that in the inner select where I have ServiceCharges?
ServiceCharges = PdfServices.Where(s => s.ServiceDescription == cy.First().ServiceDescription).Sum(y => y.TotalBill)
instead of
ServiceCharges = services.Where(x => x.CustomerId == cl.First().CustomerId && x.ServiceDescription == cy.First().ServiceDescription).Sum(y => y.TotalBill),
Thanks,
Lee.
The results that you want to re-use are members of an anonymous object, not variables. As such, you cannot expect them to be available, like a variable would be, to the inner lambda. If you re-wrote your statement, you could store them in intermediate variables:
var uniqueCustomerIdList = services
.GroupBy(x => x.CustomerId)
.Select(cl =>
{
var pdfServices = services.Where(x => x.CustomerId == cl.First().CustomerId).ToList();
return new Customer
{
CustomerId = cl.First().CustomerId,
CustomerName = cl.First().CompanyName,
PdfServices = pdfServices,
PdfServiceLines = pdfServices
.GroupBy(l => l.ServiceDescription)
.Select(cy => new PdfServiceLine
{
ServiceName = cy.First().ServiceDescription,
Quantity = cy.Count(),
UnitPrice = cy.First().PlanCharge,
ServiceCharges = services.Where(x => x.CustomerId == cl.First().CustomerId && x.ServiceDescription == cy.First().ServiceDescription).Sum(y => y.TotalBill),
UsageCharges = usage.Where(x => x.CustomerId == cl.First().CustomerId && x.ServiceDescription == cy.First().ServiceDescription).Sum(y => y.Charge),
Total = cy.Sum(c => c.PlanCharge),
}).ToList(),
PdfUsages = usage.Where(x => x.CustomerId == cl.First().CustomerId).ToList()
};
})
.ToList();
Note that the lamda body now uses curly braces and a return statement. This is not convertible to an expression tree, and some ORM frameworks, like Entity Framework, will not be able to translate the lamda into SQL.

Linq to find lasted record in group

I want add a new column to find which is lasted record in group.
Can I write subquery in Select() method?
I have try this
var test = DailyPeriods.Where(x => x.BookingDate == "2016/12/30")
.Select(x =>
new
{
PERIOD_GROUP_ID = x.PeriodGroupID,
PERIOD_NAME = x.PeriodName,
New_Column = DailyPeriods
.Where(z => z.BookingDate == "2016/12/30")
.Select(a =>
new
{
PeriodGroupID = a.PeriodGroupID,
period_name = a.PeriodName
}
)
.GroupBy(b => b.period_name)
.Select(g => g.Last().PeriodGroupID)
.Contains(x.PeriodName)
})
But will occur this error
"column not in scope: A:2211708.C(BOOKING_DATE)"
Try this..
var lastRecords = periodList.GroupBy(l => l.PeriodName)
.Select(x => new { PeriodName = x.Key,
PeriodGroupId = x.OrderBy(l => l.PeriodGroupId).Last().PeriodGroupId});
var result = from period in periodList
from lastRec in lastRecords.Where(r => r.PeriodGroupId == period.PeriodGroupId
&& r.PeriodName == period.PeriodName)
.DefaultIfEmpty()
select new { period.PeriodGroupId,period.PeriodName, New_Column=lastRec==null?false:true };

How can I do an outer join with EF 6.1

I have the following:
var tests = await db.Tests
.Include(t => t.Exam)
.Where(t => t.TestStatusId == 1)
.Select(t => new TestDTO
{
ExamName = t.Exam.Name,
Id = t.TestId,
QuestionsCount = t.QuestionsCount,
Title = t.Title
})
.ToListAsync();
return Ok(tests);
How can I make this so that it still returns tests even if there is no matching Exam for a particular test?
Try this:
//first step
var tests = db.Tests
.Include(t => t.Exam)
.Where(t => t.TestStatusId == 1);
//next step
if(testc.Exam != null && testc.Exam.Count > 0)
{
testc = testc.Select(t => new TestDTO
{
ExamName = t.Exam.Name,
Id = t.TestId,
QuestionsCount = t.QuestionsCount,
Title = t.Title
});
}

OrderByDescending doesn't work in nested linq statement

In Linqpad, i can see the correct list. But in code, after putting in the list collection, order by doesn't work for BeginDate. If i use BeginDate with Max, it works. I don't understand where i am wrong?
var templist = contentRepository
.Get(q => (q.Status == (int)StatusEnum.Active) &&
(q.CategoryId == category.GetHashCode() || q.Category.ParentId == category.GetHashCode())
&& q.MinorVersion == 0
&& q.MajorVersion > 0)
.GroupBy(q => q.VersionId)
.OrderByDescending(q => q.Key)
.Select(q => new
{
VersionId = q.Key,
Id = q.Max(x => x.Id),
MajorVersion = q.Max(x => x.MajorVersion),
UpdatedAt = q.Max(x => x.UpdatedAt),
//BeginDate = q.Max(x=>x.BeginDate),
BeginDate = (q.OrderByDescending(x => x.Id).Take(1).Select(x=>x.BeginDate)).First(),
Title = (q.OrderByDescending(x => x.Id).Take(1).Select(x => x.Title)).First(),
ShowOnHomePage = (q.OrderByDescending(x => x.Id).Take(1).Select(x=>x.ShowOnHomePage)).First()
})
.OrderByDescending(x => x.BeginDate)
.Take(maxItemCount)
.ToList();
List<ContentEntity> contents = new List<ContentEntity>();
templist.ForEach(q => contents.Add(
contentRepository
.Get(x => x.VersionId == q.VersionId && x.MajorVersion == q.MajorVersion && x.MinorVersion == 0)
.FirstOrDefault()
));
return contents.Where(q => q.ShowOnHomePage == true)
.OrderByDescending(q => q.MajorVersion)
.OrderByDescending(q => q.BeginDate)
.Take(maxItemCount)
.ToList();
You are ordering by Id, not by BeginDate. Equivalent code for
q.Max(x => x.BeginDate)
Will be
q.OrderByDescending(x => x.BeginDate).Take(1).Select(x => x.BeginDate).First()
Or simplified
q.OrderByDescending(x => x.BeginDate).First().BeginDate

Categories

Resources