LINQ to SQL: Parameterized keySelector causes local evaluation in LINQPad - linq-to-sql

I have a LINQ to SQL function that I'd allow to take the keySelector func as a paremeter:
Func<App, string> keySelector = a => a.Name;
Apps.GroupBy(keySelector).Select(g => new { Key = g.Key, Count = g.Count() }).Dump();
However, when I look at the SQL that gets generated, it's clear that everything past the initial table query is getting evaluated locally, and to make matters worse it's querying every column of the table:
SELECT [t0].[AppId], [t0].[Name], [t0].[PublisherId], [t0].[PlatformId], [t0].[UnifiedAppId]
FROM [apps].[App] AS [t0]
If I instead specify the keySelector function inline, it works as expected:
Apps.GroupBy(a => a.Name).Select(g => new { Key = g.Key, Count = g.Count() }).Dump();
SELECT COUNT(*) AS [Count], [t0].[Name] AS [Key]
FROM [apps].[App] AS [t0]
GROUP BY [t0].[Name]
Any ideas how I can get this to work? Thanks!!

Change your KeySelector to be an Expression
ie
Expression<Func<App, string>> keySelector = a => a.Name;
Apps.GroupBy(keySelector).Select(g => new { Key = g.Key, Count = g.Count() }).Dump();

Related

How to convert this request to LINQ?

SELECT StepID, count() as nb FROM Question GROUP BY StepID ORDER by nb;
You should probably go through the basics of LINQ. Microsoft Docs has a whole section dedicated to LINQ: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/
If you have your data in a List called as questions of type, List<Question> then you should be able to convert your Query like this:
var ret = from q in questions
group q by q.StepId into grouped
let count = grouped.Count()
orderby count
select new { StepId = grouped.Key, nb = count };
Query comprehension syntax:
from q in questions
group q by q.StepId into g
select new { StepId = g.Key, Count = g.Count() } into stepCount
orderby stepCount.Count
select stepCount;
Exact same in method syntax (which I prefer, since it can all query syntax can plus more and also often is more compact):
questions
.GroupBy(q => q.StepId)
.Select(g => new { StepId = g.Key, Count = g.Count() })
.OrderBy(stepCount => stepCount.Count)
Variant using another GroupBy overload:
questions
.GroupBy(q => q.StepId, (key, values) => new { StepId = key, Count = values.Count() })
.OrderBy(stepCount => stepCount.Count);

Why is LINQ to SQL Omitting Columns in Select

I'm using LINQ to SQL to select records. I need to union two queries together but the select statements are being changed so that the expressions no longer match preventing the union.
This LINQ query omits my forced columns 'resultType' and 'imageFile' from the final result.
var taglist = from t in dc.ProductTags
where t.Tag.StartsWith(prefixText)
select new AutoSearch {
resultType = "Tag",
name = t.Tag,
imageFile = string.Empty,
urlElement = t.Tag };
This is the query that is presented.
{SELECT [t0].[Tag] AS [name] FROM [dbo].[ProductTag] AS [t0] WHERE [t0].[Tag] LIKE #p0}
This is the second query to be unioned with the initial one.
var brandlist = from b in dc.Businesses
join t in dc.Tags on b.BusinessId equals t.BusinessId
where b.Name.StartsWith(prefixText)
where b.IsActive == true
where t.IsActive == true
select new AutoSearch
{
resultType = "Business",
name = b.Name,
imageFile = t.AdImage,
urlElement = b.BusinessId.ToString() };
This is the sql for the second query.
SELECT [t0].[Name] AS [name], [t1].[AdImage] AS [imageFile], CONVERT(NVarChar(MAX) [t0].[BusinessId]) AS [urlElement] FROM [dbo].[Business] AS [t0] INNER JOIN [dbo].[Tag] AS [t1] ON ([t0].[BusinessId]) = [t1].[BusinessId] WHERE ([t0].[Name] LIKE #p0)
The union... that throws the error.
var unionedResults = taglist.Union(brandlist);
The error thrown.
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
This is the AutoSearch class.
public class AutoSearch
{
public string name { get; set; }
public string imageFile { get; set; }
public string resultType { get; set; }
public string urlElement { get; set; }
}
Suggestions as to what is going???
UPDATE***
Found a work around...
Found the issue.
This is a known bug in LINQ, several discussions found here on SO that pointed me in the right direction. Turns out most of the work arounds listed on the site are no longer valid because version 4.0 of broke them too. I found another that worked..
LINQ omits duplicate values for optimization purposes. I was able to change the values of the throw away fields by converting them to strings or lower case or concatenating them.
Terribly inefficient, but it works. Whole day lost for me on this one, perhaps it will save others time.
var taglist = from t in dc.ProductTags
where t.Tag.StartsWith(prefixText)
let resultType = "Tag"
select new AutoSearch() {
resultType = resultType,
name = t.Tag,
imageFile = t.Tag.ToString(),
urlElement = t.Tag.ToLower()
};
var brandlist = from b in dc.Businesses
join t in dc.Tags on b.BusinessId equals t.BusinessId
where b.Name.StartsWith(prefixText)
where b.IsActive == true
where t.IsActive == true
where t.AdImage != null
where t.AdImage != String.Empty
let resultType = "Business"
select new AutoSearch
{
resultType = resultType,
name = b.Name,
imageFile = t.AdImage,
urlElement = b.BusinessId.ToString()
};
The only property you reference when you do the select part of your query is Tag, Linq to Sql knows this and optimizes the query to only select columns you're referencing.
In other words, this section of your query only refers to the "Tag" property, which is tied to the Tag column on your database.
new AutoSearch {
resultType = "Tag",
name = t.Tag,
imageFile = string.Empty,
urlElement = t.Tag };
What Linq does in this case is pass an expression to the underlying provider (very similar to a binary tree data structure). The provider then parses this tree and creates a SQL query from it at run time. The optimization is done by the provider at runtime which results in the SQL query you're seeing.
Update
For the second problem with the union you basically are trying to union two different SQL statements which is causing the union error. So lets take a look.
The resulting statement that would be causing the error would look something like this
SELECT [t0].[Tag] AS [name] FROM [dbo].[ProductTag] AS [t0] WHERE [t0].[Tag] LIKE #p0
UNION
SELECT [t0].[Name] AS [name], [t1].[AdImage] AS [imageFile], CONVERT(NVarChar(MAX) [t0].[BusinessId]) AS [urlElement] FROM [dbo].[Business] AS [t0] INNER JOIN [dbo].[Tag] AS [t1] ON ([t0].[BusinessId]) = [t1].[BusinessId] WHERE ([t0].[Name] LIKE #p0)
Obviously this is problametic since there is not the same number of columns between the two and that doesn't fly with SQL. While I do not have a pure linq solution there is a workaround.
First You'll need to create a SQL function that just returns a string sent to it.
CREATE FUNCTION ReturnString( #string varchar(max) )
RETURNS varchar(max)
AS
BEGIN
RETURN #string
END
GO
Next drag and drop this new SQL function into your dbml file, and finally in your query simply call the method where appropriate.
var taglist = from t in dc.ProductTags
where t.Tag.StartsWith(prefixText)
select new AutoSearch
{
resultType = dc.ReturnString("Tag"),
name = t.Tag,
imageFile = dc.ReturnString(string.Empty),
urlElement = dc.ReturnString(t.Tag)
};
var brandlist = from b in dc.Businesses
join t in dc.Tags on b.BusinessId equals t.BusinessId
where b.Name.StartsWith(prefixText)
where b.IsActive == true
where t.IsActive == true
select new AutoSearch
{
resultType = dc.ReturnString("Business"),
name = b.Name,
imageFile = t.AdImage,
urlElement = b.BusinessId.ToString()
};
Now you should be able to perform the union.

Return multiple aggregate columns in LINQ

I would like to translate the following SQL into LINQ:
SELECT
(Select count(BidID)) as TotalBidNum,
(Select sum(Amount)) as TotalBidVal
FROM Bids
I've tried this:
from b in _dataContext.Bids
select new { TotalBidVal = b.Sum(p => p.Amount), TotalBidNum = b.Count(p => p.BidId) }
but get an error "Bids does not contain a definition for "Sum" and no extension method "Sum" accepting a first argument of type "Bids" could be found.
How can I do this in LINQ?
Thanks
CONCLUDING:
The final answer was:
var ctx = _dataContext.Bids;
var itemsBid = (from b in _dataContext.Bids
select new { TotalBidVal = ctx.Sum(p => p.Amount), TotalBidNum = ctx.Count() }).First();
You can write this query using GroupBy. The Lambda expression is as follows:
var itemsBid = db.Bids
.GroupBy( i => 1)
.Select( g => new
{
TotalBidVal = g.Sum(item => item.Amount),
TotalBidNum = g.Count(item => item.BidId)
});
You could try this out. The variable b is an entity (for every iteration) while ctx is an entityset which has the extension methods you need.
var ctx = _dataContext.Bids;
var result = ctx
.Select( x => new
{
TotalBidVal = ctx.Sum ( p => p.Amount ),
TotalBidNum = ctx.Count( p => p.BidId )
} )
.First();
here's an alternative to scartag's solution:
(from b in _dataContext.Bids.Take(1)
select new
{
TotalBidVal = _dataContext.Bids.Sum(p => p.Amount),
TotalBidNum = _dataContext.Bids.Count()
}).Single();
Although there's no real reason you can't just say:
var result = new
{
TotalBidVal = _dataContext.Bids.Sum(p => p.Amount),
TotalBidNum = _dataContext.Bids.Count()
};
It hits the database twice, but its very readable
You could do it using the Aggregate Clause.
Aggregate t In _dataContext.Bids
Into TotalBidNum = Count(BidID),
TotalBidVal = Sum(Amount)
If you're using Fx4+ or an extension dll for Fx2, you could also benfit from parallelism by using
Aggregate t In _dataContext.Bids.AsParallel

Linq-to-sql - query is not filtered

I am really new to Linq and am using Linq-to-Sql as follows. However in the following example, my where clause never gets executed and the resultant query attempts to fetch all the records from my table, ignoring even the take method.
Can somebody point out as to what i am doing wrong
var baseQry = db.Table;
baseQry.Where(a => a.tab_id == theId);
baseQry.Select(o => new
{
o.name,
o.display_name,
o.type,
o.info,
time_stamp = (Convert.ToDateTime(o.timestamp).ToLongDateString())
}).Take(10);
baseQry.ToList();
Your second line...
baseQry.Where(a => a.tab_id == theId);
...is essentially a no-op, because the resulting query isn't carried over into your .Select clause.
You need to change it to this:
var baseQry = db.Table;
var results = baseQry
.Where(a => a.tab_id == theId)
.Select(o => new
{
o.name,
o.display_name,
o.type,
o.info,
time_stamp = (Convert.ToDateTime(o.timestamp).ToLongDateString())
})
.Take(10)
.ToList();

LInq-to-sql dynamic sorting problem

I have a linq-to-sql query over entity that has child entityset that I need to sort on some child fields, i.e. use this query:
var query = from p in context.Patients
let order = p.Lab_Orders.First()
orderby order.Order_Date
select p;
This query runs fine, but how would I modify it to use DLINQ OrderBy method what would I pass as a sorting parameter in run-time?
If by DLINQ you mean Dynamic Query, then you can't use the query expressions like that, you have to use extension methods with lambdas. You can start with a query expression but you have to eventually switch it over to lambda:
IEnumerable<Patient> GetPatients(string orderSortField)
{
var query =
from p in context.Patients
select new
{
Patient = p,
FirstOrder = p.Lab_Orders.First()
};
return p.OrderBy(orderSortField).Select(p => p.Patient);
}
Call it with:
var patientsByOrderDate = GetPatients("FirstOrder.Order_Date");
Use the AsQueryable() after the initial statement -
var query = from p in context.Patients
let order = p.Lab_Orders.First()
select p;
query = query.AsQueryable().OrderBy(x => x.Lab_Orders.First().OrderDate);
Try to add the "OrderBy" to the expression tree:
var query = from p in context.Patients
let order = p.Lab_Orders.First()
select p;
var x = Expression.Parameter(query.ElementType, "x");
string sortName = "order.Order_Date";
var selector = Expression.Lambda(Expression.PropertyOrField(x, sortName), x);
query = query.Provider.CreateQuery(
Expression.Call(typeof(Queryable), "OrderBy",
new Type[] { query.ElementType, selector.Body.Type },
query.Expression, selector)
) as IQueryable<Patients>;
Needs the namespace "System.Linq.Expressions".