Need to convert sql statement into Entity Framework equivalent - sql-server

Given the following tables:
InstrumentLogs
InstrumentLogId (PK, int, not null)
InstrumentId (FK, int, not null)
LogDate (datetime, not null)
Action (string, not null)
Instruments
InstrumentId (PK, int, not null)
CountyId (FK, int, not null)
Counties
CountyId (PK, int, not null)
StateFips (FK, int, not null)
Name (string, not null)
States
StateFips (PK, int, not null)
Name (string, not null)
I need to figure out how to write this SQL query using Entity Framework:
select s.StateName, count(*) as total
from instruments as i
join counties as c on i.CountyID = c.CountyID
join states as s on s.StateFIPS = c.StateFIPS
where i.InstrumentID in
(select i1.InstrumentId from InstrumentLogs as i1 where i1.action = 'review' and
i1.logdate in (select max(logdate) from instrumentlogs as i2 where i1.instrumentid
=i2.InstrumentID group by i2.instrumentid))
group by s.StateName
I tried something along the lines of:
_context.Instruments.Include(i => i.County.State)
.Where(i => _context.Logs.Where(l => l.Action == 'review'
&& _context.Logs.Where(l2 => l2.InstrumentId == l.InstrumentId).Max(l2 => l2.LogDate) == l.LogDate).GroupBy(i => i.County.State.Name)
.Select(g => new { State = g.Key.Name, Total = g.Count() });
However, EF doesn't like this. I wind up with the error stating that only primitive types or enumeration types are supported.
Thanks for your help.

I finally got it to work like this
var _query = _dataContext.IndexLogs.GroupBy(l => l.InstrumentId, l => l)
.Select(grp => new { Id = grp.Key, Date = grp.Max(g => g.ActionDate) });
//Prequery to find log records that match given type and action type
var _indexes = _dataContext.IndexLogs.Where(l => l.Action == type
&& _query.Contains(new { Id = l.InstrumentId, Date = l.ActionDate})).Select(i => i.InstrumentId);
var _states = _dataContext.AdvancedIndexes
.Include(i => i.County.State)
.Where(a => a.Id > 0 && _indexes.Contains(a.Id))
.GroupBy(i => new { i.County.State.Id, i.County.State.Name })
.Select(g => new { fips = g.Key.Id, name = g.Key.Name, count = g.Count() });

Related

how to join a table in asp.netcore

Here the way I join the table I want to implement it to asp.net core I don't know which is the correct way.
left join MasterHubxRangeTest mhr on hb.categoryid=mhr.hubxcatid and mhr.ItemStatus='Normal' and mhr.CountryCode='AUS' and mhr.ItemTitle = hb.ItemTitle
below line of code is how i tried to implement the same in asp.net core here im using Include() function i think this is wrong which function should i use. Here HubxDataItems, HubxDataCategory, MasterHubxRangeTest are tables. I'm trying to get the data by giving some cnditions.
The issue is only MasterHubxRangeTest table condition that I gave
var dataItems =
(from item in _context.HubxDataItems.Where(c => c.PatientId == patientId && c.IsActive == true && c.IsDeleted == false)
from category in _context.HubxDataCategory.Where(c => c.Id == item.CategoryId && c.IsActive == true && c.IsDeleted == false)
from range in _context.MasterHubxRangeTest.Include(d => d.HubXCatID == item.CategoryId && d.ItemTitle == item.ItemTitle && d.ItemStatus == "Normal" && d.CountryCode == "AUS").Where(c => c.HubXCatID == item.CategoryId && c.IsActive == true && c.IsDeleted == false)
select new
{
CategoryId = item.CategoryId,
ItemTitle = item.ItemTitle,
ItemValue = item.ItemValue,
ItemUnit = category.CategoryName.Trim().ToLower() == "notes" ? category.CategoryName : item.ItemUnit,
DisplayOrder = category.DisplayOrder,
IsActive = item.IsActive,
IsDeleted = item.IsDeleted,
CreatedDate = item.CreatedDate,
CreatedBy = item.CreatedBy,
UpdatedBy = item.UpdatedBy,
UpdatedDate = item.UpdatedDate,
PatientId = item.PatientId,
CategoryName = category.CategoryName,
NormalRange = range.ItemValue,
ItemColor = range.ItemColor
}).AsEnumerable();

EF Core 3.1 - Database scalar function - string.Join

Since string.Join(...) is not translatable in the latest EF Core and a lot of our queries do use that method, I'm currently trying to implement it with user-defined functions. I can see that the function actually works in the ssms but I can't get it to work in c#.
The method needs to accept an array of strings as an input so I created a user-defined table type:
IF TYPE_ID('[system].[stringList]') IS NULL
BEGIN
CREATE TYPE system.stringList AS TABLE (val nvarchar(max))
END
GO
Then I created the function itself:
create function toCommaSeparatedString(#strings [system].[stringList] readonly)
returns nvarchar(max)
as
begin
DECLARE #values nvarchar(max)
SELECT #values = COALESCE(#values + ', ', '') + [val]
FROM #strings
return #values
end
I can verify that it works by simply executing the following sql in the ssms:
declare #input [system].[stringList]
insert into #input values ('1'), ('2'), ('3'), ('4')
select dbo.toCommaSeparatedString(#input)
In c# I declared the function on my DbContext (I tried both string[] and IEnumerable<string>):
[DbFunction("toCommaSeparatedString", "dbo")]
public static string ToCommaSeparatedString(string[] strings) => throw new NotImplementedException();
And used it:
...
var output = MyDbContext.ToCommaSeparatedString(new [] { "1", "2" });
...
but I'm getting an exception:
"The parameter 'strings' for the DbFunction 'BusinessUnitDbContext.ToCommaSeparatedString' has an invalid type 'string[]'. Ensure the parameter type can be mapped by the current provider.",
Is it possible to achieve what I'm trying to achieve here? Should I also configure that function on the DbContext?
EDIT:
Here is my custom projection that i want to use to create the viewmodel:
private static IQueryable<IView> ProjectToJobOverView(this IQueryable<Job> entities)
{
return entities.Select(j => new JobOverView
{
Id = j.EntityId,
Deleted = j.Deleted.HasValue,
Name = j.Name,
Status = j.Status.ToString(),
NumberOfInstructionSets = j.Tasks.Count(t => t.InstructionSet != null),
NumberOfCompletedInstructionSets = j.Tasks.Count(t => t.InstructionSet.IsCompleted),
NumberOfOrderLines = j.Tasks
.SelectMany(t => t.TaskOrderLines).Select(x => x.OrderLineId)
.Distinct()
.Count(),
Details = j.Tasks
.OfType<StockMovementTask>()
.Where(t => t.InstructionSet != null)
.Select(t => t.InstructionSet)
.Select(s => new JobOverviewDetail
{
Id = s.EntityId,
Deleted = s.Deleted.HasValue,
State = s.Tasks.First().OperationState.ToString(),
DeliveryMethod = BusinessUnitDbContext.ToCommaSeparatedString(
s.Tasks
.First()
.TaskOrderLines.Where(x => x.OrderLine.Order is OutgoingOrder && (x.OrderLine.Order as OutgoingOrder).DeliveryType != null)
.Select(x => (x.OrderLine.Order as OutgoingOrder).DeliveryType.Name).ToArray()),
}),
Categories = j.Tasks.Select(t => t.Process.ProcessDefinition.ProcessDefinitionGroup.ProcessDefinitionCategory.ToString()).Distinct().ToArray(),
OrderNumbers = j.Tasks.OfType<StockMovementTask>().SelectMany(t => t.TaskOrderLines).Select(j => j.OrderLine.Order.Number).Distinct().ToArray(),
});
}

Insert Additional Condition in LINQ

I am modifying a LINQ query in SQL Server 2014, but I have never used the syntax:
if (!UserAccessMatrixSession.HasRole(Session, Constant.ROLE_STAFF))
{
if (country != 0 )
{
ViewData["Employees"] = (from staff in db.Staffs
from jobinfo in db.JobInfo
.Where(x => x.staff_id == staff.StaffID)
.OrderByDescending(x => x.jobinfo_id).Take(1)
orderby staff.Alias
select new { staff, jobinfo }).Where(x => x.jobinfo.location == country)
.Select(x => x.staff).ToList();
}
else
{
ViewData["Employees"] = (from staff in db.Staffs
orderby staff.Alias
select staff).ToList();
}
}
I would like to insert an additional condition as follows:
where jobinfo.last_date == null OR DateTime.Now < jobinfo.last_date
I believe you would want to add that where clause into where you are selecting from the job info
if (!UserAccessMatrixSession.HasRole(Session, Constant.ROLE_STAFF))
{
if (country != 0 )
{
ViewData["Employees"] = (from staff in db.Staffs
from jobinfo in db.JobInfo
.Where(x => x.staff_id == staff.StaffID)
.OrderByDescending(x => x.jobinfo_id).Take(1)
orderby staff.Alias
select new { staff, jobinfo }).Where(x => x.jobinfo.location == country
/* last_date null or UtcNow < last_date*/ && (x.jobinfo.last_date == null || DateTime.UtcNow < jobinfo.last_date))
.Select(x => x.staff).ToList();
}
else
{
ViewData["Employees"] = (from staff in db.Staffs
orderby staff.Alias
select staff).ToList();
}
}

Query Slow in Linq, Fast in LinqPad, SQL Management Studio and SQL Profiler

I have this linq query i'm using and it's taking 50 seconds to run when i am running it my asp.net application, however the same query executes in 500ms in LinqPad and Sql Management Studio.
I even took the query from the SQL Profiler and ran it again in SQL Management Studio and it takes around 500ms. What overhead Linq could be doing, that an extra 49s??
Below is the code for reference, thanks for your help.
var rCampaign =
(from a in db.AdCreative
join h in db.AdHit on a.ID equals h.AdID into gh
join l in db.AdGroup_Location on a.AdGroupID equals l.AdGroupID into gj
from subloc in gj.DefaultIfEmpty()
from subhits in gh.DefaultIfEmpty()
where a.AdGroup.AdHost.Select(q => q.ID).Contains(rPlatform.ID) &&
a.AdGroup.AdPublisher.Select(q => q.ID).Contains(rPublisher.ID) &&
a.AdDimensionID == AdSize &&
a.AdGroup.Campaign.Starts <= rNow &&
a.AdGroup.Campaign.Ends >= rNow &&
subhits.HitType == 1 &&
(subloc == null || subloc.LocationID == rLocationID)
select new {
ID = a.ID,
Name = a.Name,
Spent = (subhits.AdDimension != null) ? ((double)subhits.AdDimension.Credit / 1000) : 0,
CampaignID = a.AdGroup.Campaign.ID,
CampaignName = a.AdGroup.Campaign.Name,
CampaignBudget = a.AdGroup.Campaign.DailyBudget
}).GroupBy(adgroup => adgroup.ID)
.Select(adgroup => new {
ID = adgroup.Key,
Name = adgroup.FirstOrDefault().Name,
Spent = adgroup.Sum(q => q.Spent),
CampaignID = adgroup.FirstOrDefault().CampaignID,
CampaignName = adgroup.FirstOrDefault().CampaignName,
CampaignBudget = adgroup.FirstOrDefault().CampaignBudget,
})
.GroupBy(q => q.CampaignID)
.Select(campaigngroup => new {
CampaignID = campaigngroup.Key,
DailyBudget = campaigngroup.FirstOrDefault().CampaignBudget,
Consumed = campaigngroup.Sum(q => q.Spent),
RemainningCredit = campaigngroup.FirstOrDefault().CampaignBudget - campaigngroup.Sum(q => q.Spent),
Ads = campaigngroup.Select(ag => new {
ID = ag.ID,
Name = ag.Name,
Spent = ag.Spent
}).OrderBy(q => q.Spent)
})
.Where(q => q.Consumed <= q.DailyBudget).OrderByDescending(q => q.RemainningCredit).First();
There are a few ways you can simplify that query:
select into lets you keep it all in query syntax.
The join ... into/from/DefaultIfMany constructs implementing left joins can be replaced with join ... into construcs representing group joins.
Some of the groups near the end cannot be empty, so FirstOrDefault is unnecessary.
Some of the where conditions can be moved up to the top before the query gets complicated.
Here's the stab I took at it. The revisions were significant, so it might need a little debugging:
var rCampaign = (
from a in db.AdCreative
where a.AdDimensionID == AdSize &&
a.AdGroup.Campaign.Starts <= rNow &&
a.AdGroup.Campaign.Ends >= rNow &&
a.AdGroup.AdHost.Select(q => q.ID).Contains(rPlatform.ID) &&
a.AdGroup.AdPublisher.Select(q => q.ID).Contains(rPublisher.ID)
join hit in db.AdHit.Where(h => h.HitType == 1 && h.LocationID == rLocationID)
on a.ID equals hit.AdID
into hits
join loc in db.AdGroup_Location
on a.AdGroupID equals loc.AdGroupID
into locs
where !locs.Any() || locs.Any(l => l.LocationID == rLocationID)
select new {
a.ID,
a.Name,
Spent = hits.Sum(h => h.AdDimension.Credit / 1000) ?? 0,
CampaignID = a.AdGroup.Campaign.ID,
CampaignName = a.AdGroup.Campaign.Name,
CampaignBudget = a.AdGroup.Campaign.DailyBudget,
} into adgroup
group adgroup by adgroup.CampaignID into campaigngroup
select new
{
CampaignID = campaigngroup.Key,
DailyBudget = campaigngroup.First().CampaignBudget,
Consumed = campaigngroup.Sum(q => q.Spent),
RemainingCredit = campaigngroup.First().CampaignBudget - campaigngroup.Sum(q => q.Spent),
Ads = campaigngroup.Select(ag => new {
ag.ID,
ag.Name,
ag.Spent,
}).OrderBy(q => q.Spent)
} into q
where q.Consumed <= q.DailyBudget
orderby q.RemainingCredit desc)
.First()
I refactored using Query syntax (Not sure if it improved readability). Removed one group by. Made some minor adjustments (replaced FirstOrDefault with Key property, changed Contains to Any). Hopefully it has some effect of speed.
var rCampaign = (from cg in
(from a in db.AdCreative
from subhits in db.AdHit.Where(h => a.ID == h.AdID)
.DefaultIfEmpty()
from subloc in db.AdGroup_Location.Where(l => a.AdGroupID == l.AdGroupID)
.DefaultIfEmpty()
where a.AdGroup.AdHost.Any(q => q.ID == rPlatform.ID) &&
a.AdGroup.AdPublisher.Any(q => q.ID == rPublisher.ID) &&
a.AdDimensionID == AdSize &&
a.AdGroup.Campaign.Starts <= rNow &&
a.AdGroup.Campaign.Ends >= rNow &&
subhits.HitType == 1 &&
(subloc == null || subloc.LocationID == rLocationID)
group new { a, subhits } by new { ID = a.ID, a.Name, CampaignID = a.AdGroup.Campaign.ID, CampaignName = a.AdGroup.Campaign.Name, CampaignBudget = a.AdGroup.Campaign.DailyBudget } into g
select new
{
ID = g.Key.ID,
Name = g.Key.Name,
Spent = g.Sum(x => (x.subhits.AdDimension != null) ? ((double)subhits.AdDimension.Credit / 1000) : 0),
CampaignID = g.Key.CampaignID,
CampaignName = g.Key.CampaignName,
CampaignBudget = g.Key.CampaignBudget
})
group cg by new { cg.CampaignID, cg.CampaignBudget } into cg
let tempConsumed = cg.Sum(q => q.Spent)
let tempRemainningCredit = cg.Key.CampaignBudget - tempConsumed
where tempConsumed <= cg.Key.CampaignBudget
orderby tempRemainningCredit desc
select new
{
CampaignID = cg.Key.CampaignID,
DailyBudget = cg.Key.CampaignBudget,
Consumed = tempConsumed,
RemainningCredit = tempRemainningCredit,
Ads = from ag in cg
orderby ag.Spent
select new
{
ID = ag.ID,
Name = ag.Name,
Spent = ag.Spent
}
}).First();

How to write this SQL average query in linq

Here's the SQL I would like run:
DECLARE #StartDt DATETIME, #EndDt DATETIME
SET #StartDt='2009-01-01'
SET #EndDt='2010-06-01'
SELECT
AVG(DATEDIFF(day, CreatedDt, CompletedDt))
AVG(DATEDIFF(day, CreatedDt, ComplianceDt))
FROM MyTable
WHERE RequestDt BETWEEN #StartDt AND #EndDt
Can this be expressed in Linq (C#) and have it all run on the database?
DateTime startDt = new DateTime(2009, 1, 1);
DateTime endDt = new DateTme(2010, 6, 1);
var query = dc.MyTables
.Where(x => startDt <= x.RequestDate && x.RequestDt <= endDt)
.GroupBy(x => 1) //unsure about this group by
.Select(g => new
{
FirstAvg = g.Average(x =>
SqlMethods.DateDiffDay(x.CreatedDt, x.CompletedDt)),
SecondAvg = g.Average(x =>
SqlMethods.DateDiffDay(x.CreatedDt, x.ComplianceDt))
});
var row = query.Single();

Resources