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".
Related
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();
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);
Given a list IQueryables, how can you sum the count of each, without having multiple statements executed in the database?
return queries
.Sum(qy=> qy.Count());
The above works, but hits the database for each query.
You can first use the Aggregate function with Concat to combine the IQueryable's and then Count the total like this:
return queries.Aggregate((x,y) => x.Concat(y)).Count()
Starting from this idea Sum(q1,q2) = q1.Concat(q2).Count() I've tested the following extensions:
public static class LinqExtensions
{
public static IQueryable<object> ConcatAny<T,R>(this IQueryable<T> q1, IQueryable<R> q2)
{
return q1.Select(c=>(object)null).Concat(q2.Select(c=>(object)null));
}
public static IQueryable<object> ConcatAll(this IEnumerable<IQueryable<object>> queries)
{
var resultQuery = queries.First();
foreach (var query in queries.Skip(1))
{
resultQuery = resultQuery.ConcatAny(query);
}
return resultQuery;
}
}
I assumed you have heterogeneous queries like IQueryable<T>, IQueryable<R> so on and you are interested in counting all rows no matter which the source is.
So you might use these extensions like
var q1 = Table1.AsQueryable();
var q2 = Table2.AsQueryable();
var q3 = Table3.AsQueryable();
var queries = new IQueryable<object>[] {q1,q2,q3}; // we use here the covariance feature
return queries.ConcatAll().Count();
The generated SQL might look like this
SELECT COUNT(*) AS [value]
FROM (
SELECT NULL AS [EMPTY]
FROM (
SELECT NULL AS [EMPTY]
FROM [Table1] AS [t0]
UNION ALL
SELECT NULL AS [EMPTY]
FROM [Table2] AS [t1]
) AS [t2]
UNION ALL
SELECT NULL AS [EMPTY]
FROM [Table3] AS [t3]
) AS [t4]
I don't think is very effective though
Ok, a few minutes late, but I got it!
Here is the code:
public static class LinqExtensions
{
public static int CountAll(this IEnumerable<IQueryable<object>> queries)
{
if (queries == null || !queries.Any())
{
throw new ArgumentException("Queries parameter cannot be null or empty");
}
Expression ex = Expression.Constant(0);
foreach (var qy in queries)
{
// create count expression
var expression = Expression.Call(
typeof(Queryable),
"Count",
new[] { qy.ElementType },
qy.Expression
);
ex = Expression.Add(ex, expression);
}
return queries.First().Provider.Execute<int>(ex);
}
}
You use it as queries.CountAll() where queries is an IEnumerable<IQueryable<object>> as in Adrian's answer or even simple IEnumerable<IQueryable>.
Here is a sample SQL result from the profiler:
exec sp_executesql N'SELECT #p0 + ((
SELECT COUNT(*)
FROM [A] AS [t0]
WHERE [t0].[i1] >= #p1
)) + ((
SELECT COUNT(*)
FROM [B] AS [t1]
WHERE [t1].[i2] >= #p2
)) + ((
SELECT COUNT(*)
FROM [C] AS [t2]
WHERE [t2].[i3] >= #p3
)) AS [value]',N'#p0 int,#p1 int,#p2 int,#p3 int',#p0=0,#p1=2,#p2=2,#p3=2
Which is the representation of
var a = db.GetTable<A>();
var b = db.GetTable<B>();
var c = db.GetTable<C>();
var q1 = a.Where(v => v.i1 >= 2);
var q2 = b.Where(v => v.i2 >= 2);
var q3 = c.Where(v => v.i3 >= 2);
var queries = new IQueryable<object>[] {
q1,q2,q3
};
Note that A, B and C are different objects/tables with different numbers of properties/columns and that the expressions are random Where filters.
If you are using Entity Framework you can use an extension called EntityFramework.Extended. There is a built in extension called Future Queries. This will allow you to specify that a query should be executed the next time a trip to the database is made.
NuGet command:
Install-Package EntityFramework.Extended
Sample code:
static void Main(string[] args)
{
using (var context = new MyDbContext())
{
var modelSet1 = context.Models.Where(x => x.ModelId < 25).FutureCount();
var modelSet2 = context.Models.Where(x => x.ModelId > 25 && x.ModelId < 32).FutureCount();
var modelSet3 = context.Models.Where(x => x.ModelId > 32).FutureCount();
var queries = new [] {modelSet1, modelSet2, modelSet3};
var countQueries = queries.Sum(x => x.Value);
Console.WriteLine(countQueries);
}
Console.ReadLine();
}
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.
I'm building an ad-hoc query to send to SQL doing the following:
var data = from d in db.xxxx select d;
foreach (pattern in user_stuff)
data = data.Where(d=>SqlMethods.Like(d.item,pattern)==true);
The problem is that the WHERE clauses get AND'ed together. I need OR's.
Any idea how to get an OR?
How about I answer my own question:
PredicateBuilder
You need to construct an Expression to represent the composite OR predicate, then pass that to Where:
var data = from d in db.xxxx select d;
Expression<Func<Data, bool>> predicate = null;
foreach (var pattern in user_stuff)
{
Expression<Func<Data, bool>> newPredicate = d => SqlMethods.Like(d..item, pattern));
if (predicate == null) predicate = newPredicate;
else predicate = Expression.OrElse(predicate, newPredicate);
}
return data.Where(predicate);
EDIT: This is probably what PredicateBuilder does, only a lot messier :)