How do fetch data from multiple tables with method syntax without using joins, but only .where() methods?
I'm making a select against EF5 db context which maps to this legacy table structure where I have a persons detail table and another table which refers both to itself to create a hierarchy and to the person details table this way:
PersonSet
.Where(p => p.LastName.ToLower()=="surname")
.Join(SubsetSet, p => p.Id, ss => ss.SubsetLink, (p, ss) => new { PersonDetail = p, person = ss })
.Where(m => m.person.Frame=="a" && m.person.Purpose=="1")
.Join(SubsetSet, ss1 => ss1.person.Owner, person => person.SubsetLink, (ss1, ss2) => new { person = ss1, club = ss2 })
.Where(a => a.club.Frame=="b" && a.club.Purpose=="2")
.Join(SubsetSet, ss => ss.club.Owner, ss2 => ss2.SubsetLink, (ss, ss2) => new { club = ss, association = ss2 })
.Where(a => a.association.Frame=="b" && a.association.Purpose=="3")
.Join(SubsetSet, ss => ss.association.Owner, ss3 => ss3.SubsetLink, (ss, ss3) => new { association = ss, district = ss3})
.Where(d => d.district.Frame=="b" && d.district.Purpose=="4" && d.district.SubsetLink=="12345")
.Select(proj => new { proj.association.club.person, proj.association.club, proj.association, proj.district })
.OrderByDescending(a => a.association.club.person.phyperson.FirstName)
.Take(10).Dump();
The above query works at least in LinqPad but, it seems to me that If I could get rid of those joins the statement might look a bit nicer. Now I know, like in the Albahari example below, that this can be done with query syntax. But I couldn't find an example that would illustrate this situation with method syntax. The way I'm trying to approach this might of course be wrong and that's why I can't find suitable examples.
Here I found something similar, but couldn't make it work in LinQPad:
Is multiple .Where() statements in LINQ a performance issue?
Or this one, where the solution is again in query syntax:
Cross Join with Where clause
Or this example by Albahari: (http://www.linqpad.net/WhyLINQBeatsSQL.aspx)
from p in db.Purchases
where p.Customer.Address.State == "WA" || p.Customer == null
where p.PurchaseItems.Sum (pi => pi.SaleAmount) > 1000
select p
Consider this query:
var q = from order in orders
from orderline in order.Lines
where orderline.Count > 10
select order.Discount * orderline.Price;
this more or less corresponds to
var q = orders
.SelectMany(order => order.Lines, (order, orderline) => new { order, orderline})
.Where(item => item.orderline.Count > 10)
.Select(item => item.order.Discount * item.orderline.Price);
For more information on SelectMany, see the MSDN documentation.
If you don't have associations defined:
var q = from order in orders
from orderline in orderLines
where orderline.OrderId == order.Id
where orderline.Count > 10
select order.Discount * orderline.Price;
this more or less corresponds to
var q = orders
.SelectMany(order => orderLines, (order, orderline) => new { order, orderline})
.Where(item => item.orderline.OrderId == item.order.Id)
.Where(item => item.orderline.Count > 10)
.Select(item => item.order.Discount * item.orderline.Price);
Related
I have 2 queries with LINQ first one is lambda expression and second is only standard LINQ. I want to get the same output in both, but the 2nd query doesn't give me an output.
How I can solve this and what is the logic of joining a table 2 times?
var query1 = _context.UserEdu.Where(x => x.personId == id && x.status ==PersonEducationStatus.SA.ToString())
.Join(_context.Educations.Where(x => x.statusCode == ACTIVE), pe => pe.classNum, s => s.classNum, (pe, class) => class)
.Join(_context.Educations.Where(x => x.isActive), s => s.eduNum, defaultS => defaultS.eduNum, (class, defaultclass) => new
{ class, defaultclass.classNum })
.Join(_context.EducationDocument.Where(...),
s => s.classNum,
rd => rd.entity_id,
(class, rd) => new
{
... output
});
var query2 = (from pedu in _context.UserEdu.Where(x => x.personId == id && x.status == PersonEducationStatus.SA.ToString())
join class in _context.Educations.Where(x => x.statusCode == ACTIVE) on pedu.classNum equals class.classNum
join defaultclass in _context.Educations.Where(x => x.isActive) on pedu.classNum equals defaultclass.classNum
join rd in _context.EducationDocument.Where(...) on defaultclass.classNum equals rd.entity_id
select new
{
... output same with first query
});
Well,
pedu.classNum equals defaultclass.classNum
Is not
s => s.eduNum, defaultS => defaultS.eduNum
IOW, you have used in second join different keys.
I have a following working SQL query:
SELECT * FROM truck t
WHERE t.currentlocationdbid IN (SELECT dbid FROM location WHERE name = 'Los Angeles')
OR t.nextdestinationdbid IN (SELECT dbid FROM location WHERE name = 'Chicago' OR name = 'New York');
I'd like to write this in NHibernate. With multiple trips to DB for each entity it works, of course, but I'd like to make it a one trip. Looked into examples with detached queries like this, this or this but none worked for me. Tried to do it also with aliases and criterias.
One of dozens of attempts:
var subQuery1 = QueryOver.Of<LocationEntity>().Where(l => l.Name == LocationNameEnum.LA);
var subQuery2 = QueryOver.Of<LocationEntity>().Where(l => l.Name == LocationNameEnum.CHG || l.Name == LocationNameEnum.NY);
var poc = session.QueryOver<TruckEntity>()
.WithSubquery.WhereProperty(t => t.CurrentLocation).In(subQuery1)
.WithSubquery.WhereProperty(t => t.NextDestination).In(subQuery2)
.List<TruckEntity>();
Thanks in advance for any suggestion.
You got it almost right, you are only missing the .Where(Restrictions.Disjunction()...) for the or in SQL.
Based on your code (assuming that you have a property Id in LocationEntity):
// get IDs to look for in CurrentLocation
var subQuery1 = QueryOver.Of<LocationEntity>()
.Where(l => l.Name == LocationNameEnum.LA)
.Select(x => x.Id);
// get IDs to look for in NextDestination
var subQuery2 = QueryOver.Of<LocationEntity>()
.Where(l => l.Name == LocationNameEnum.CHG || l.Name == LocationNameEnum.NY)
.Select(x => x.Id);
var poc = session.QueryOver<TruckEntity>()
.Where(Restrictions.Disjunction() // this takes care of the OR
.Add(Subqueries.WhereProperty<TruckEntity>(x => x.CurrentLocation.Id).In(subQuery1))
.Add(Subqueries.WhereProperty<TruckEntity>(x => x.NextDestination.Id).In(subQuery2))
)
.List<TruckEntity>();
I have this join query that pulls all school programs and products that is in a person's shopping cart:
//this pulls all items the user purchased
var poop = Context.Query<Cart>().Where(x => x.UserId == currentUserId && x.Status == "Archived")
.Select(
p => new
{
p.ItemId,
p.TypeId,
p.PurchaseDate
})
//This get the media type name of the cart items
.Join(
Context.Query<MediaType>(),
t => new {t.TypeId},
m => new {TypeId = m.Id},
(t, m) => new
{
t.ItemId,
t.TypeId,
t.PurchaseDate,
m.TypeName
}).OrderBy(d => d.PurchaseDate)
//Now i need specifics of the items like name, sku, etc. StartDate will be null for items that are products, but contains DateTime for items that are programs.
.Join(
Context.Query<ProgramProductView>(),
e => new {e.ItemId, e.TypeId},
prog => new {ItemId = prog.Id, prog.TypeId},
(e, prog) => new
{
e.ItemId,
e.TypeId,
e.PurchaseDate,
e.TypeName,
prog.FullName,
prog.StartDate,
prog.Sku,
prog.Closed
}).OrderBy(d => d.PurchaseDate);
So right there is where it crashes because prog.StartDate is null for products. I get SQL is not available error.
Is there a way to have the join allow null-able fields? I am only using lambda because it's easier to read and clean.
Well you just need to use Nullable<> property for you anonymous class in your last Join:
.Join(
Context.Query<ProgramProductView>(),
e => new {e.ItemId, e.TypeId},
prog => new {ItemId = prog.Id, prog.TypeId},
(e, prog) =>
new
{
...
(DateTime?)prog.StartDate,
...
}).OrderBy(d => d.PurchaseDate);
Hope it will help.
I'm working on a report right now that runs great with our on-premises DB (just refreshed from PROD). However, when I deploy the site to Azure, I get a SQL Timeout during its execution. If I point my development instance at the SQL Azure instance, I get a timeout as well.
Goal: To output a list of customers that have had an activity created during the search range, and when that customer is found, get some other information about that customer regarding policies, etc. I've removed some of the properties below for brevity (as best I can)...
UPDATE
After lots of trial and error, I can get the entire query to run fairly consistently within 1000MS so long as this block of code is not executed.
CurrentStatus = a.Activities
.Where(b => b.ActivityType.IsReportable)
.OrderByDescending(b => b.DueDateTime)
.Select(b => b.Status.Name)
.FirstOrDefault(),
With this code in place, things begin to go haywire. I think this Where clause is a big part of it: .Where(b => b.ActivityType.IsReportable). What is the best way to grab the status name?
EXISTING CODE
Any thoughts as to why SQL Azure would timeout whereas on-premises would turn this around in less than 100MS?
return db.Customers
.Where(a => a.Activities.Where(
b => b.CreatedDateTime >= search.BeginDateCreated
&& b.CreatedDateTime <= search.EndDateCreated).Count() > 0)
.Where(a => a.CustomerGroup.Any(d => d.GroupId== search.GroupId))
.Select(a => new CustomCustomerReport
{
CustomerId = a.Id,
Manager = a.Manager.Name,
Customer = a.FirstName + " " + a.LastName,
ContactSource= a.ContactSource!= null ? a.ContactSource.Name : "Unknown",
ContactDate = a.DateCreated,
NewSale = a.Sales
.Where(p => p.Employee.IsActive)
.OrderByDescending(p => p.DateCreated)
.Select(p => new PolicyViewModel
{
//MISC PROPERTIES
}).FirstOrDefault(),
ExistingSale = a.Sales
.Where(p => p.CancellationDate == null || p.CancellationDate <= myDate)
.Where(p => p.SaleDate < myDate)
.OrderByDescending(p => p.DateCreated)
.Select(p => new SalesViewModel
{
//MISC PROPERTIES
}).FirstOrDefault(),
CurrentStatus = a.Activities
.Where(b => b.ActivityType.IsReportable)
.OrderByDescending(b => b.DueDateTime)
.Select(b => b.Disposition.Name)
.FirstOrDefault(),
CustomerGroup = a.CustomerGroup
.Where(cd => cd.GroupId == search.GroupId)
.Select(cd => new GroupViewModel
{
//MISC PROPERTIES
}).FirstOrDefault()
}).ToList();
I cannot give you a definite answer but I would recommend approaching the problem by:
Run SQL profiler locally when this code is executed and see what SQL is generated and run. Look at the query execution plan for each query and look for table scans and other slow operations. Add indexes as needed.
Check your lambdas for things that cannot be easily translated into SQL. You might be pulling the contents of a table into memory and running lambdas on the results, which will be very slow. Change your lambdas or consider writing raw SQL.
Is the Azure database the same as your local database? If not, pull the data locally so your local system is indicative.
Remove sections (i.e. CustomerGroup then CurrentDisposition then ExistingSale then NewSale) and see if there is a significant performance improvement after removing the last section. Focus on the last removed section.
Looking at the line itself:
You use ".Count() > 0" on line 4. Use ".Any()" instead, since the former goes through every row in the database to get you an accurate count when you just want to know if at least one row satisfies the requirements.
Ensure fields referenced in where clauses have indexes, such as IsReportable.
Short answer: use memory.
Long answer:
Because of either bad maintenance plans or limited hardware, running this query in one big lump is what's causing it to fail on Azure. Even if that weren't the case, because of all the navigation properties you're using, this query would generate a staggering number of joins. The answer here is to break it down in smaller pieces that Azure can run. I'm going to try to rewrite your query into multiple smaller, easier to digest queries that use the memory of your .NET application. Please bear with me as I make (more or less) educated guesses about your business logic/db schema and rewrite the query accordingly. Sorry for using the query form of LINQ but I find things such as join and group by are more readable in that form.
var activityFilterCustomerIds = db.Activities
.Where(a =>
a.CreatedDateTime >= search.BeginDateCreated &&
a.CreatedDateTime <= search.EndDateCreated)
.Select(a => a.CustomerId)
.Distinct()
.ToList();
var groupFilterCustomerIds = db.CustomerGroup
.Where(g => g.GroupId = search.GroupId)
.Select(g => g.CustomerId)
.Distinct()
.ToList();
var customers = db.Customers
.AsNoTracking()
.Where(c =>
activityFilterCustomerIds.Contains(c.Id) &&
groupFilterCustomerIds.Contains(c.Id))
.ToList();
var customerIds = customers.Select(x => x.Id).ToList();
var newSales =
(from s in db.Sales
where customerIds.Contains(s.CustomerId)
&& s.Employee.IsActive
group s by s.CustomerId into grouped
select new
{
CustomerId = grouped.Key,
Sale = grouped
.OrderByDescending(x => x.DateCreated)
.Select(new PolicyViewModel
{
// properties
})
.FirstOrDefault()
}).ToList();
var existingSales =
(from s in db.Sales
where customerIds.Contains(s.CustomerId)
&& (s.CancellationDate == null || s.CancellationDate <= myDate)
&& s.SaleDate < myDate
group s by s.CustomerId into grouped
select new
{
CustomerId = grouped.Key,
Sale = grouped
.OrderByDescending(x => x.DateCreated)
.Select(new SalesViewModel
{
// properties
})
.FirstOrDefault()
}).ToList();
var currentStatuses =
(from a in db.Activities.AsNoTracking()
where customerIds.Contains(a.CustomerId)
&& a.ActivityType.IsReportable
group a by a.CustomerId into grouped
select new
{
CustomerId = grouped.Key,
Status = grouped
.OrderByDescending(x => x.DueDateTime)
.Select(x => x.Disposition.Name)
.FirstOrDefault()
}).ToList();
var customerGroups =
(from cg in db.CustomerGroups
where cg.GroupId == search.GroupId
group cg by cg.CustomerId into grouped
select new
{
CustomerId = grouped.Key,
Group = grouped
.Select(x =>
new GroupViewModel
{
// ...
})
.FirstOrDefault()
}).ToList();
return customers
.Select(c =>
new CustomCustomerReport
{
// ... simple props
// ...
// ...
NewSale = newSales
.Where(s => s.CustomerId == c.Id)
.Select(x => x.Sale)
.FirstOrDefault(),
ExistingSale = existingSales
.Where(s => s.CustomerId == c.Id)
.Select(x => x.Sale)
.FirstOrDefault(),
CurrentStatus = currentStatuses
.Where(s => s.CustomerId == c.Id)
.Select(x => x.Status)
.FirstOrDefault(),
CustomerGroup = customerGroups
.Where(s => s.CustomerId == c.Id)
.Select(x => x.Group)
.FirstOrDefault(),
})
.ToList();
Hard to suggest anything without seeing actual table definitions, espectially the indexes and foreign keys on Activities entity.
As far I understand Activity (CustomerId, ActivityTypeId, DueDateTime, DispositionId). If this is standard warehousing table (DateTime, ClientId, Activity), I'd suggest the following:
If number of Activities is reasonably small, then force the use of CONTAINS by
var activities = db.Activities.Where( x => x.IsReportable ).ToList();
...
.Where( b => activities.Contains(b.Activity) )
You can even help the optimiser by specifying that you want ActivityId.
Indexes on Activitiy entity should be up to date. For this particular query I suggest (CustomerId, ActivityId, DueDateTime DESC)
precache Disposition table, my crystal ball tells me that it's dictionary table.
For similar task to avoid constantly hitting Activity table I made another small table (CustomerId, LastActivity, LastVAlue) and updated it as the status changed.
This is driving me mad. I thought it seemed simple enough but the below is returning a list of IEnumerable containing the entities I need, instead of just a list of entities:
db.tblPeople.Where(p => p.id == id).Select(s => s.tblCars.Select(z => z.tblCarType)).ToList();
My attempt is to retrieve a list of all carType entities associated with the personId.
I assume it's something to do with the last nested select?
Do like this because you are expecting multiple records to be returned:
var result = db.tblPeople
.Where(p => p.id == id)
.Select(s => s.tblCars
.SelectMany(z => z.tblCarType)).ToList();
Use SelectMany in order to flatten IEnumerable<IEnumerable<CarType>> into IEnumerable<CarType>.
var carTypes =
db.tblPeople
.Where(p => p.id == id)
.SelectMany(s =>
s.tblCars
.Select(z => z.tblCarType))
.ToList();
This translates from
var carTypes =
(from person in tblPeople
from car in person.tblCar
from carType in car.tblCarType
where person.id == id
select carType).ToList();
This is what you want/need:
db.tblPeople.Where(p => p.id == id).SelectMany(s => s.tblCars.Select(z => z.tblCarType)).ToList();