Converting T-SQL to Linq - tsql

I am using Entitry Framework 4.1 and I am struggling to understand how the convert the below query which uses joins and aggregate methods to a Linq to Entities call in the DomainService.
SELECT tblTime.Period As Timeline, COUNT(tblEngineeringDashboard_ItemList.ID) AS Items
FROM tblEngineeringDashboard_ItemList INNER JOIN
tblTime ON tblEngineeringDashboard_ItemList.TimeID = tblTime.ID
GROUP BY tblTime.Period
ORDER BY tblTime.Period
Can anyone provide help.
Possible Solution
Dim var = From i In ObjectContext.tblEngineeringDashboard_ItemList
Join t In ObjectContext.tblTimes On i.TimeID Equals t.ID
Group By i.TimeID Into Group
Select DateStart = (From n In ObjectContext.tblTimes Where n.ID = TimeID Select n.Period), PartCount = Group.Count
Phil

The first thing which comes to mind is:
var q = from t in Context.Time
group t by t.Period into g
orderby g.Key
select new
{
Timeline = g.Key,
Items = (from ti in g
from il in ti.ItemList // or whatever the property for the navigation to tblEngineeringDashboard_ItemList is called
select il).Count()
};
However, the original SQL had an INNER JOIN, which would reject tblTime records without any matching records in tblEngineeringDashboard_ItemList. So you may want:
var q = from t in Context.Time
where t.ItemList.Any()
group t by t.Period into g
orderby g.Key
select new
{
Timeline = g.Key,
Items = (from ti in g
from il in ti.ItemList // or whatever the property for the navigation to tblEngineeringDashboard_ItemList is called
select il).Count()
};
You can also flip the query around:
var q = from i in Context.EngineeringDashboardItemList
where i.Time != null
group i by i.Time.Period into g
orderby g.Key
select new
{
Timeline = g.Key,
Items = g.Count()
};

Does this work?
tblEngineeringDashboard_ItemList
.Join(tblTime,ed => ed.TimeID ,t => t.ID, (ed,t) => new{ed,t})
.GoupBy(g => g.t.Period)
.Select(s => new
{
Timeline = s.Key,
Items = s.Count()
}
)
.OrderBy(o => o.Timeline)

While converting from sql to linq isn't an ideal approach (you should think directly in linq, translating your need to a linq query), the query you posted is rather simple.
var grouped = tblTime.OrderBy(c => c.Period).GroupBy(c => c.Period).Select(c =>
new {
timeline = c.Key,
count = c.SelectMany(x => x.tblEngineeringDashboard).Count()
});
*Edit: There, fixed. Everything on the L2E engine.
This provided that there are correct foreign keys between the tables (thus you don't have to declare the join manually).

Related

Take each first element of the group by

How can I take each first element in the group with EF 5 ?
var result = await context.SomeDbSet
.Where(...)
.GroupBy(x => new { x.SomeField, ... })
.Select(x => x.First())
.ToListAsync();
I am getting not supported exception.
How to correctly rewrite query? Thanks.
You cannot do that with grouping. SQL has a limitation - with GROUP BY you can select only grouping keys and aggregation result. This limitation for sure extended to LINQ to Entities - after GroupBy you can select only grouping keys and aggregation result.
Such result can be achieved by SQL and Window functions:
SELECT
r.*,
FROM
(
SELECT
s.*,
ROW_NUMBER() OVER(PARTITION BY s.SomeField1, s.SomeField2 ORDER BY s.SomeDate) AS RN
FROM SomeDbSet s
WHERE ...
) r
WHERE r.RN = 1
For those who want to stay with LINQ, I propose extension (disclaimer: I'm extension creator) linq2db.EntityFrameworkCore
And you can write query above via LINQ
var rnQuery =
from s in context.SomeDbSet
where ...
select new
{
Data = s,
RN = Sql.Ext.RowNumber().Over()
.PartitionBy(s.SomeField1, s.SomeField2)
.OrderBy(s.SomeDate)
.ToValue()
}
var resultQuery = await rnQuery
.Where(r => r.RN == 1)
.Select(r => r.Data)
.ToLinqToDB();
var result = resultQuery.ToList();
// async variant may need ToListAsyncLinqToDB() call
// because of collision in async extension methods between EF Core and linq2db
var result = await resultQuery.ToListAsyncLinqToDB();

Can't add/sum string values in IEnumerable

The ultimate goal is to display a GrandTotal column using Highcharts. The GrandTotal should be the sum of TotalAmount for a given Offer id. TotalAmount is a string and the values are like $10.00 or 10.00. GrandTotal is an int, but could easily be changed. Here is what I have done so far.
Step 1) Convert the two IEnumerable lists into their ViewModel counterparts. I set GrandTotal to 0 here because I don't know the amount.
var offersConvert = offers
.Select(o => new OfferSummaryViewModel
{
Id = o.Id,
Name = o.Name,
Created = o.Created,
Shares = o.Shares,
Redemptions = o.Redemptions,
GrandTotal = 0
})
.ToList();
var sharedOffersConvert = sharedOffers
.Select(s => new SharedOfferViewModel
{
OfferId = s.OfferId,
//TotalAmount = s.TotalAmount.Replace("$", string.Empty).Replace(",", string.Empty).Trim()
TotalAmount = s.TotalAmount
})
//.Where(i => i.TotalAmount != null)
.ToList();
Step 2) Join the two lists on the Id of the Offer.
var data = offersConvert
.Join(sharedOffersConvert,
o => o.Id,
s => s.OfferId,
(o, s) => new { offersConvert = o, sharedOffersConvert = s })
.Select(o => new
{
Id = o.offersConvert.Id,
Created = o.offersConvert.Created,
Shares = o.offersConvert.Shares,
Redemptions = o.offersConvert.Redemptions,
Name = o.offersConvert.Name,
OfferId = o.sharedOffersConvert.OfferId,
TotalAmount = o.sharedOffersConvert.TotalAmount,
//GrandTotal = Convert.ToInt32(o.sharedOffersConvert.TotalAmount.Replace("$", string.Empty).Replace(",", string.Empty).Trim())
//GrandTotal = Convert.ToInt32(Convert.ToDouble(o.sharedOffersConvert.TotalAmount, CultureInfo.InvariantCulture))
})
//.Where(o => o.Id == o.OfferId)
.OrderBy(o => o.Created.Add(offset))
.ToList();
As you can tell, I've tried to remove any dollar signs and commas. I've even tried to trim white space in order to get clean data. I am then trying to convert the strings to int values, so I can sum them. Nothing seems to work. I've even tried .GroupBy and other methods (see below). At least with .GroupBy I can get to the .Sum operator. With the other method I run into issues when I can't convert int into ToList(), so I have to try and convert ToString().
.Where(o => o.Id == o.OfferId)
.GroupBy(g => g.Id)
.Select(x => new { GrandTotal = x.Sum(o => o.TotalAmount) })
ERROR in above: can't convert TotalAmount to decimal
.Where(i => i.Id == i.OfferId)
.Sum(i => Convert.ToInt32(Convert.ToDouble(i.TotalAmount, CultureInfo.InvariantCulture)
)).ToString()
Does anyone know how I can add/sum the string values in TotalAmount to get a GrandTotal per Offer id?
Any help us much appreciated. Thanks!
UPDATE: This works, but I really don't understand why and I don't think it is very clean. I really couldn't find many examples where people were joining two lists together and summing one of the columns. This seems pretty common to me, but perhaps it is not.
var data = (from o in offersConvert
join s in sharedOffersConvert on o.Id equals s.OfferId
orderby o.Created.Add(offset)
let k = new
{
Id = o.Id,
Name = o.Name,
Created = o.Created,
Shares = o.Shares,
Redemptions = o.Redemptions
}
group s by k into totals
select new
{
OfferId = totals.Key.Id,
Name = totals.Key.Name,
Created = totals.Key.Created,
Shares = totals.Key.Shares,
Id = totals.Key.Id,
Redemptions = totals.Key.Redemptions,
GrandTotal = totals.Sum((s => s.TotalAmount == null ? Decimal.Zero : Decimal.Parse(s.TotalAmount, NumberStyles.Currency)))
})
.ToList();
You can use
Decimal.TryParse("$10.00", NumberStyles.Currency, CultureInfo.CurrentCulture, out var res);
Or in the context of your LINQ,
GrandTotal = Decimal.Parse(o.sharedOffersConvert.TotalAmount, NumberStyles.Currency)
if o.sahredOffersConvert.TotalAmount may be null,
GrandTotal = (o.sharedOffersConvert.TotalAmount ==null) ? Decimal.Zero : Decimal.Parse(o.sharedOffersConvert.TotalAmount, NumberStyles.Currency)
I'm still puzzled why everyone lately has this odd fascination with the Lambda syntax. And that seems to have been only one of several means you've used to make this more complicated than necessary:
var data = (from o in offers
join s in sharedOffers on o.Id equals s.OfferId
orderby o.Created.Add(offset)
select new
{
Id = o.Id,
Created = o.Created,
Shares = o.Shares,
Redemptions = o.Redemptions,
Name = o.Name,
OfferId = o.OfferId,
TotalAmount = Decimal.Parse(o.TotalAmount, NumberStyles.Currency)
})
.ToList();
Further, as offset seems to be a constant (for the life of this query), adding it to Created isn't going to affect the ordering, and that bit can be removed.
And, since it appears your final output is just the grand totals, it can be reduced further to:
var data = (from o in offers
join s in sharedOffers on o.Id equals s.OfferId
orderby o.Created
group Decimal.Parse(o.TotalAmount, NumberStyles.Currency) by o.id into totals
select new
{
Id = totals.Key,
GrandAmount = totals.Sum()
})
.ToList();
UPDATE: Putting back what I took out... This should work (I don't have your tables, so I can't test it)
var data = (from o in offers
join s in sharedOffers on o.Id equals s.OfferId
orderby o.Created.Add
group o by o.Id into totals
let item = totals.First()
select new
{
Id = item.Id,
Created = item.Created,
Shares = item.Shares,
Redemptions = o.Redemptions,
Name = item.Name,
OfferId = item.OfferId,
GrandTotal = totals.Sum(t=>Decimal.Parse(t.TotalAmount, NumberStyles.Currency))
})
.ToList();

How can I fix linq query to select count of ids with group by?

I want to create this SQL query to linq:
SELECT
COUNT(m.FromUserId) AS Messages,
m.FromUserId AS UserId
FROM
dbo.ChatMessages m
INNER JOIN
dbo.ChatMessagesRead mr ON mr.ChatMessageId = m.ChatMessageId
WHERE
m.ToUserId = #toUserId
GROUP BY
m.FromUserId
I have tried create following linq query:
var messages = from m in _dbContext.ChatMessages
join mread in _dbContext.ChatMessagesRead on m.ChatMessageId equals mread.ChatMessageId
where m.ToUserId == userId
group m by m.FromUserId into g
select new
{
UserId = g.Key,
Messages = g.Count()
};
var messagesList = messages.ToList();
But this doesn't work.
How can I fix this linq query?
I get this exception:
Expression of type 'System.Func2[Microsoft.Data.Entity.Query.EntityQueryModelVisitor+TransparentIdentifier2[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],System.Int32]' cannot be used for parameter of type 'System.Func2[<>f__AnonymousType12[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],System.Int32]' of method 'System.Collections.Generic.IEnumerable1[System.Linq.IGrouping2[System.Int32,Project.BL.ChatMessages.ChatMessages]] _GroupBy[<>f__AnonymousType12,Int32,ChatMessages](System.Collections.Generic.IEnumerable1[<>f__AnonymousType12[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead]], System.Func2[<>f__AnonymousType12[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],System.Int32], System.Func2[<>f__AnonymousType1`2[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],Project.BL.ChatMessages.ChatMessages])'"
I'm facing the same issue and I've found that there is an opened issue on the Entity Framework Core bugtracker
The only workaround for now seems to split the request in two.
var filtered = (from m in _dbContext.ChatMessages
join mread in _dbContext.ChatMessagesRead on m.ChatMessageId equals mread.ChatMessageId
where m.ToUserId == userId
select m).ToList();
var messages = from m in filtered
group m by m.FromUserId into g
select new
{
UserId = g.Key,
Messages = g.Count()
};
you can try this
var res = ctx.MyTable // Start with your table
.GroupBy(r => r.id) / Group by the key of your choice
.Select( g => new {Id = g.Key, Count = g.Count()}) // Create an anonymous type w/results
.ToList(); // Convert the results to List
Your code should work. However I created another version of your query using extension methods.
var messages =
_dbContext
.ChatMessages
.Where(message => message.ToUserId == userId)
.Join(
_dbContext.ChatMessageRead,
message => message.ChatMessageId,
readMessage => readMessage.ChatMessageId,
(m, mr) => m.FromUserId
)
.GroupBy(id => id)
.Select(group =>
new
{
UserId = group.Key,
Messages = group.Count()
}
);
Could you please try it if it also throws the same exception or not?

EntityFramework do Paging on a query with a join

I have a query with a left join in it:
var query = (from v in context.Vehicles
//left join vehicleAttributes
join va in context.VehicleAttributes on v.VehicleId equals va.VehicleId into vAttributes
from vehicleAttributes in vAttributes.DefaultIfEmpty()
where v.FleetId == fleetId
select new { v, vehicleAttributes });
And now I need to do a paging on it.
this works but gets all rows, so much more than i actually need
query.ToList().Select(x => x.v).Distinct().Skip(10 * (page - 1)).Take(10).ToList();
this is what I tried instead but now I don't have the joint values
query.Select(x => x.v).Distinct().ToList().Skip(10 * (page - 1)).Take(10).ToList();
any ideas?
Thanks
The ToList() triggers the call to the database so you need to only do this after you apply the Skip and Take. You'll need an OrderBy clause as well.
You should be able to do something like this:
var data = (from v in context.Vehicles
join va in context.VehicleAttributes on v.VehicleId equals va.VehicleId into vAttributes
from vehicleAttributes in vAttributes.DefaultIfEmpty()
where v.FleetId == fleetId
select new { v, vehicleAttributes })
.OrderBy(p => p.v.FleetId)
.Skip(10 * (page - 1))
.Take(10)
.ToList();
Don't call ToList before Skip. Doing so will return all the records in the database that match your query.
To use Skip on an EntityFramework query, you need to have an instance of IOrderedQueryable, so you need an OrderBy clause.
query
.Select(x => x.v)
.Distinct()
.OrderBy(v => v.FleetId)
.Skip(10 * (page - 1))
.Take(10).ToList();
I've got a project that takes care of a lot of this functionality. It's available on NuGet (with an MVC counterpart) and Google Code.
Using it would look something like this:
var factory = new Pagination.PageSourceFactory {
MaxItemsPerPage = 50,
DefaultItemsPerPage = 20
};
var source = factory.CreateSource(query, page);

EF Left joining a table on two properties combined with a case statement

I'm trying to write a query for a database that will left join a table to a look up table and the results will be returned based on a case statement.
In normal SQL the query would look like this:
SELECT chis_id, chis_detail, cilt.mhcatID, cilt.mhtID, 'TheFileName' =
CASE
WHEN cilt.mhcatID IS NOT NULL AND cilt.mhtID IS NOT NULL THEN chis_linked_filename
END
FROM chis
LEFT JOIN cilt on cilt.mhcatID = chis.mhcat_id AND cilt.mhtID = chis.mht_id
WHERE cch_id = 50
chis is the table being queried, cilt is a look-up table and does not contain any foreign key relationships to chis as a result (chis has existing FK's to mht and mhcat tables by the mhtID and mhcatID respectively).
The query will be used to return a list of history updates for a record. If the join to the cilt lookup table is successful this means that the caller of the query will have permission to view the filename of any associated files for the history updates.
Whilst during my research I've found various posts on here relating on how to do case statements and left joins in Linq to Entity queries, I've not been able to work out how to join on two different fields. Is this possible?
You need to join on an anonymous type with matching field names like so:
var query = from x in context.Table1
join y in context.Table2
on new { x.Field1, x.Field2 } equals new { y.Field1, y.Field2 }
select {...};
A full working example using the an extra from instead of a join would look something like this:
var query = from chis in context.Chis
from clit in context.Clit
.Where(x => x.mhcatID = chis.mhcat_id)
.Where(x => x.mhtID = chis.mht_id)
.DefaultIfEmpty()
select new
{
chis.id,
chis.detail,
cilt.mhcatID,
cilt.mhtID,
TheFileName = (cilt.mhcatID != null && cilt.mhtID != null) ? chis.linked_filename : null
};
Based on what Aducci suggested, I used a group join and DefaultIsEmpty() to get the results I wanted. For some reason, I couldn't get DefaultIfEmpty() didn't work correctly on its own and the resulting SQL employed an inner join instead of a left.
Here's the final code I used to get the left join working:
var query = (from chis in context.chis
join cilt in context.cilts on new { MHT = chis.mht_id, MHTCAT = chis.mhcat_id } equals new { MHT = cilt.mhtID, MHTCAT = cilt.mhcatID } into tempCilts
from tempCilt in tempCilts.DefaultIfEmpty()
where chis.cch_id == 50
select new {
chisID = chis.chis_id,
detail = chis.chis_detail,
filename = chis.chis_linked_filename,
TheFileName = (tempCilt.mhcatID != null && tempCilt.mhtID != null ? chis.chis_linked_filename : null),
mhtID = chis.mht_id,
mhtcatID = chis.mhcat_id
}).ToList();