C# Linq equivalent for SQL script not responding - c#

I have the following SQL script which works fine and pretty fast:
select top 30 CONVERT(date, p.DateCreated) as Fecha,
(select count(*) from People d where d.recipientid = p.recipientid and d.SubscriptionType = 0 and CONVERT(date, p.DateCreated) = CONVERT(date, d.DateCreated)) as Subscribed
from People p
where p.RecipientId = '276643679047355'
group by CONVERT(date, p.DateCreated), p.RecipientId
order by CONVERT(date, p.DateCreated) desc;
However, when I'm trying to call this from a C# application using LinQ, it doesn't respond as expected (as a matter of fact, after waiting 5min, I must say it doesn't respond at all). I came up with the following LinQ command, nevertheless, something has to be wrong because its not responding as fast as the SQL script provided:
model = await _context.People
.Where(x => x.RecipientId == recipientId && x.DateCreated > startDate && x.DateCreated < endDate)
.Select(x => new { DateGrouped = x.DateCreated.ToString("yyyy-MM-dd"), x.RecipientId })
.GroupBy(x => new { x.DateGrouped, x.RecipientId })
.Select(a => new StatsViewModel
{
DateStatsFormatted = a.Key.DateGrouped,
Subscribed = _context.People.Where(d => d.RecipientId == a.Key.RecipientId && d.SubscriptionType == SubscriptionType.Suscribed && d.DateCreated.ToString("yyyy-MM-dd") == a.Key.DateGrouped).Count()
}
)
.ToListAsync();
Could you please help me to point out what I'm doing wrong or, at least, suggest me what to search?

Well, after a long afternoon trying different approaches, I've finally figured it out.
I couldn't get to work my SQL profiler because my database is on Azure and it seems its not compatible. Date formatting wasn't my solution neither. The issue was on my nested query for "Subscribed" property... it seems it was creating a sort of circular reference.
Here is the way is works now:
model = await _context.People
.Where(x => x.RecipientId == recipientId && x.DateCreated > startDate && x.DateCreated < endDate)
.Select(x => new { DateGrouped = x.DateCreated.ToString("yyyy-MM-dd"), x.RecipientId, x.SubscriptionType })
.GroupBy(x => new { x.DateGrouped, x.RecipientId })
.Select(a => new StatsViewModel
{
DateStatsFormatted = a.Key.DateGrouped,
Subscribed = a.Count(c=>c.SubscriptionType == SubscriptionType.Suscribed),
Unsubscribed = a.Count(c=>c.SubscriptionType == SubscriptionType.Unsuscribed),
NoSet = a.Count(c=>c.SubscriptionType == SubscriptionType.NoSet)
}
)
.ToListAsync();
Notice that I've only added a property "SubscriptionType" on my main select in order to use it later on the filtered select as part of the filter of the "Subscribed" property... pretty simple, straight forward and works like a charm!

Related

Convert SQL Query to LINQ Lambda C#

I have to fix a query which was already written in the LINQ Lambda, I found the fix in a Simple SQL Query but now I have some trouble in converting it to LINQ Query,
Here is my SQL Query
select * from RequestItem_SubRequestItem x
where x.RequestItem_key = 1 and x.SubRequestItem_key in (
select o.SubRequestItem_key
from SubRequestItem_Entitlement o
inner join SubRequestItem sr on sr.SubRequestItem_key = o.SubRequestItem_key
where o.Entitlement_key = 2 and sr.Action = 'Add' )
And below is my LINQ C# code where I am trying to insert my fixes which include inner join.
z.Entitlements = ARMContext.Context.SubRequestItem_Entitlement
.Where(o => o.Entitlement_key == z.AccessKey && !o.Role_key.HasValue && o.Entitlement.EntitlementConfiguration.UserVisible == true
&& (ARMContext.Context.RequestItem_SubRequestItem
.Where(x => x.RequestItem_key == requestItemKey)
.Select(y => y.SubRequestItem_key)
.Contains(o.SubRequestItem_key)))
.Join(ARMContext.Context.SubRequestItems, subrq => subrq.SubRequestItem_key, temp => requestItemKey, (subrq, temp) => subrq == temp)
Where as previously the C# LINQ code looked like this
z.Entitlements = ARMContext.Context.SubRequestItem_Entitlement
.Where(o => o.Entitlement_key == z.AccessKey && !o.Role_key.HasValue && o.Entitlement.EntitlementConfiguration.UserVisible == true
&& (ARMContext.Context.RequestItem_SubRequestItem
.Where(x => x.RequestItem_key == requestItemKey)
.Select(y => y.SubRequestItem_key)
.Contains(o.SubRequestItem_key)))
When I try to insert the JOIN in the LINQ as per my conditions then I get to see this error.
What is my mistake? Can anybody tell me a correct way to do it?
I think this should Suffice your need, although you might have to make changes to the other code which are dependent on your SubRequestItem_Entitlement table with {user, add}
please have a look at that. As I am sure you will have to make those changes.
.Join(ARMContext.Context.SubRequestItems, user => user.SubRequestItem_key, subreqItems => subreqItems.SubRequestItem_key, (user, subreqItems) => new { user, subreqItems })
.Where(Action => Action.subreqItems.Action == z.ApprovalAction)
you can use this query. I exactly matched the SQL query
var query = ARMContext.Context.RequestItem_SubRequestItem
.Where(a => a.RequestItem_key == 1 && a.RequestItem_key == (ARMContext.Context.SubRequestItem_Entitlement
.Join(ARMContext.Context.SubRequestItems,
right => right.SubRequestItem_key,
left => left.SubRequestItem_key,
(right, left) => new
{
right = right,
left = left
})
.Where(x => x.right.Entitlement_key == 2 && x.left.Action == "Add" && x.right.SubRequestItem_key == a.RequestItem_key).Select(y => y.right.SubRequestItem_key)).FirstOrDefault());

NHibernate multiple subqueries with in clause

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

SQL Azure vs. On-Premises Timeout Issue - EF

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.

Query execution time in Entity Framework vs in SQL Server

I have a query written in Linq To Entities:
db.Table<Operation>()
.Where(x => x.Date >= dateStart)
.Where(x => x.Date < dateEnd)
.GroupBy(x => new
{
x.EntityId,
x.EntityName,
x.EntityToken
})
.Select(x => new EntityBrief
{
EntityId = x.Key.EntityId,
EntityName = x.Key.EntityName,
EntityToken = x.Key.EntityToken,
Quantity = x.Count()
})
.OrderByDescending(x => x.Quantity)
.Take(5)
.ToList();
The problem is that it takes 4 seconds when executing in the application using EF. But when I take the created pure SQL Query from that query object (using Log) and fire it directly on SQL Server, then it takes 0 seconds. Is it a known problem?
Firstly, try improving your query:
var entityBriefs =
Table<Operation>().Where(x => x.Date >= dateStart && x.Date < dateEnd)
.GroupBy(x => x.EntityId)
.OrderByDescending(x => x.Count())
.Take(5)
.Select(x => new EntityBrief
{
EntityId = x.Key.EntityId,
Quantity = x.Count()
});
var c = entityBriefs.ToDictionary(e => e.EntityId, e => e);
var entityInfo = Table<Operation>().Where(o => mapping.Keys.Contains(o.EntityId).ToList();
foreach(var entity in entityInfo)
{
mapping[entity.EntityId].EntityName = entity.EntityName;
mapping[entity.EntityId].EntityToken = entity.EntityToken;
}
You may also compile queries with the help of CompiledQuery.Compile, and use it further with improved performance.
http://msdn.microsoft.com/en-us/library/bb399335%28v=vs.110%29.aspx
The problem was with the database locks. I used wrong isolation level, so my queries were blocked under some circumstances. Now I use read-commited-snapshot and the execution time looks good.

Comparing nullable datetime

I am trying to compare dates in my linq query but my c.DateRequired is a nullable date field and I want to just compare the date without the time. How do I go about converting c.DateRequired in this case to just get the date.
IEnumerable<SalesOrder> salesOrders = _uow.Repository<SalesOrder>().Query()
.Filter(c => c.IsDeleted != true &&
(((c.DateRequired == DateTime.Today.Date)) && Period == 1) ||
(((c.DateRequired >= sDateP && c.DateRequired <= stDate)) &&
(Period == 2 || Period == 3)))
.OrderBy(q => q.OrderBy(c => c.DateCreated))
.Get()
.Select(p => new
{
SalesOrder = p,
SalesOrderDetailsList =
p.SalesOrderDetailsList.Where(pd => pd.IsDeleted != true),
salesOrderDetailsComponentsList =
p.SalesOrderDetailsList.Select(c => c.SalesOrderDetailsComponentsList),
SalesOrderDetailsComponentsInfoList =
p.SalesOrderDetailsList.Select(
i =>
i.SalesOrderDetailsComponentsList.Select(
info => info.SalesOrderDetailsComponentsInfoList))
})
.ToList()
.Select(p => p.SalesOrder);
return salesOrders;
}
Try to use something like this:
.Where(x => x.DateRequired.HasValue &&
EntityFunctions.TruncateTime(x.DateRequired.Value) == EntityFunctions.TruncateTime(DateTime.Today))
.ToList();
It works perfectly for me.
you need to use EntityFunctions.TruncateTime which accept a nullable datetime to truncate time in linq
EntityFunctions.TruncateTime(x.DateRequired) == EntityFunctions.TruncateTime(DateTime.Today)
I answer to a similar question.
The recommended way to compare dates in linq queries if you are using EntityFramework 6 is DbFunctions.TruncateTime(m.PlanDate) and for previous versions EntityFunctions.TruncateTime(m.PlanDate)

Categories