I have using PredicateBuilder class to dynamically creating Where clause in LINQ statements.
Following is my code which is When using predicate
var predicate = PredicateBuilder.True<tbl_login>();
predicate = predicate.And(x => x.DAT_LOGIN_TIME >= startDate && x.DAT_LOGIN_TIME <= endDate);`
var data = context.tbl_login.Join(context.tbl_user, x => x.LNG_USER_PRIMARY_ID, y => y.LNG_USER_PRIMARY_ID, (x, y) => new
{
userID = x.LNG_USER_PRIMARY_ID,
loginTime = x.DAT_LOGIN_TIME,
ageGroup = y.INT_AGE_GROUP
}).Where(predicate)
.Select(x => new
{
userID = x.userID,
ageGroup = x.ageGroup
}).Distinct().ToList();
It gives me compile time exception cannot convert from System.Linq.Expressions.Expression<System.Func<JoyRydeAnalysis.Data.tbl_login,bool>>' to System.Linq.Expressions.Expression<System.Func<AnonymousType#1,int,bool>
What i am missing?
Your predicate is a predicate for tbl_login. But here, you're projecting to an anonymous type:
(x, y) => new
{
userID = x.LNG_USER_PRIMARY_ID,
loginTime = x.DAT_LOGIN_TIME,
ageGroup = y.INT_AGE_GROUP
}
That predicate can't be applied to that projection.
I suspect you may want to apply the predicate earlier instead though:
var data = context.tbl_login
.Where(predicate)
.Join(...)
.Select(...)
.Distinct()
.ToList();
Note that at this point, your Select will be redundant, as you can just change the projection in the Join call.
It's also note clear why you're using PredicateBuilder at all, to be honest. You're only joining PredicateBuilder.True with a single expression - so you can just use:
var data = context.tbl_login
.Where(x => x.DAT_LOGIN_TIME >= startDate && x.DAT_LOGIN_TIME <= endDate)
.Join(...)
.Select(...)
.Distinct()
.ToList();
Related
I'm trying to convert a sql stored proc to linq. I'm having issues with the groupby and inner joins.
Here is what I've tried:
var r = _context.Table1
.GroupBy(x => new { x.OptionId, x.Years, x.Strike })
.Join(_context.Table2,
oc => oc.OptionId, o => o.OptionId, (oc, o) => new
{
OptionsCosts = oc,
Options = o
}).Where(x => x.Options.OptionType == 1
&& x.Options.QualifierId != null
&& x.Options.CreditingMethod != "xxx")
.Select(y => new DataModel.Table1()
{
Years = y.Select(a => a.OptionsCosts.Years).FirstOrDefault(),
Strike = y.Select(a => a.OptionsCosts.Strike).FirstOrDefault(),
Value = y.Select(a => a.OptionsCosts.Value).FirstOrDefault(),
ChangeUser = y.Select(a => a.OptionsCosts.ChangeUser).FirstOrDefault(),
ChangeDate = DateTime.Now,
OptionId = y.Select(a => a.OptionsCosts.OptionId).FirstOrDefault()
});
Here is the SQL that I'm trying to convert:
SELECT o2.OptionId, o2.Years, o2.Strike, SUM(d2.Weights) as 'TotalWeight', COUNT(*) as 'Counts'
FROM Table1 o2
INNER JOIN #Dates d2 --this is a temp table that just holds dates. I was thinking just a where statement could do it???
ON d2.EffectiveDate = o2.EffectiveDate
INNER JOIN Table2 od2
ON od2.OptionId = o2.OptionId
AND od2.OptionType = 1
AND od2.qualifierid is null
AND od2.CreditingMethod <> 'xxx' --28095
GROUP BY o2.OptionId,o2.Years, o2.Strike
My data is way off so I'm sure I'm doing something wrong.
var table1=_context.Table1
.groupBy(o2=> new{
o2.OptionId
, o2.Years
, o2.Strike
})
.select(s=> new{
s.key.OptionId
, s.key.Years
, s.key.Strike
,TotalWeight=s.sum(x=>x.Weights)
,Counts=o2.count(c=>c.OptionId)
}).tolist();
var result=table1
.Join(_context.Table2,oc => oc.OptionId, o => o.OptionId, (oc, o) => new{ OptionsCosts = oc, Options = o })
.Where(x => x.Options.OptionType == 1
&& x.Options.QualifierId != null
&& x.Options.CreditingMethod != "xxx")
.select(x=> new {
x.oc.OptionId, x.oc.Years, x.oc.Strike, x.oc.TotalWeight, x.oc.Counts
}).tolist();
Small advise, when you rewriting SQL queries, use LINQ Query syntax which is close to SQL and more effective to avoid errors.
var dates = new List<DateTime>() { DateTime.Now }; // fill list
var query =
from o2 in _context.Table1
where dates.Contains(o2.EffectiveDate)
from od2 in _context.Table1.Where(od2 => // another way to join
od2.OptionId == o2.OptionId
&& od2.OptionType == 1
&& od2.qualifierid == null
&& od2.CreditingMethod != "xxx")
group o2 by new { o2.OptionId, o2.Years, o2.Strike } into g
select new
{
g.Key.OptionId,
g.Key.Years,
g.Key.Strike,
Counts = g.Count()
// SUM(d2.Weights) as 'TotalWeight', -this one is not available because dates in memory
};
If you are on start and trying to rewrite procedures on LINQ - EF Core is bad idea. Too limited IQueryable support and usually you will fight for each complex LINQ query.
Try linq2db which has temporary tables support and your stored proc can be rewritten into identical LINQ queries. Or you can use linq2db.EntityFrameworkCore to extend EF Core functionality.
Disclaimer. I’m creator of this extension and one from linq2db creators.
I have the below Linq expression
var orderByLambda = Expression.Lambda<Func<Queue, int>>(
nullCheckExpression, parameterExpression);
queue = context.Table
.Join(context.tablea, cq => cq.a, r => r.a, (cq, r) => new { cq, r })
.Join(context.tableb, s => s.r.b, se => se.b, (s, se) => new { s, se })
.Join(context.tablec, u => u.s.cq.c, us => us.c, (u, us) => new { u, us })
.Where(cq => cq.u.s.cq.c == Utilities.Authentication.c)
.Where(cq => buildStatusOrder.Contains((BuildStatusEnum)cq.u.s.cq.d))
.OrderBy(o => o.u.se.b)
.Select(s => new QueueInfo
{
x = s.u.c,
y = s.u.d,
z = s.u.a
});
queue = queue.OrderBy(f => orderByLambda);
var concat = queue.GroupBy(e => new { e.x, e.y, e.z })
.OrderBy(v => v.FirstOrDefault().segmentID)
.ToList()
.Select(ss => new QueueInfo
{
x = ss.x,
y = ss.y,
z = ss.z,
})
.AsQueryable();
I am getting below error in concat
The LINQ expression node type 'Lambda' is not supported in LINQ to Entities.
What went wrong in my code?
Instead of
queue = queue.OrderBy(f => orderByLambda);
Use:
queue = queue.OrderBy(orderByLambda);
Two notes on your code:
LINQ to Entities (Entity Framework) wants to translate your query into SQL, so it can only do operations that it knows how to translate. For example, you can't use most common LINQ collection methods such as Contains, etc.
You have defined a lambda object, but not what the actual expression does, to use for sorting - or at least, you haven't shown us what nullCheckExpression and parameterExpression are. In general, this is not how you would sort LINQ to Entities anyway - it should be something like queue.OrderBy(f => f.x).ThenBy(f => f.y); - you have to define which fields from your select are actually used for the sort. (See point number 1)
Consider the following Linq to Entities query:
return (from lead in db.Leads
join postcodeEnProvincie in postcodeEnProvincies
on lead.Postcode equals postcodeEnProvincie.Postcode
where (lead.CreationDate >= range.StartDate) && (lead.CreationDate <= range.EndDate)
group lead by postcodeEnProvincie.Provincie into g
select new Web.Models.GroupedLeads() {
GroupName = g.Key,
HotLeads = g.Count(l => l.Type == Data.LeadType.Hot),
Leads = g.Count(),
PriorityLeads = g.Count(l => l.Type == Data.LeadType.Priority),
Sales = g.Count(l => l.Sold),
ProductA = g.Count(l => l.Producten.Any(a => ((a.Name.Equals("productA", StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals("productA", StringComparison.CurrentCultureIgnoreCase))))),
ProductB = g.Count(l => l.Producten.Any(a => ((a.Name.Equals("productB", StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals("productB", StringComparison.CurrentCultureIgnoreCase))))),
ProductC = g.Count(l => l.Producten.Any(a => ((a.Name.Equals("productC", StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals("productC", StringComparison.CurrentCultureIgnoreCase))))),
ProductC = g.Count(l => l.Producten.Any(a => ((a.Name.Equals("productD", StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals("productD", StringComparison.CurrentCultureIgnoreCase)))))
}).ToList();
If you're anything like me, your toes curl at the repetition of the product selection logic. This pattern is repeated in another place as well. I first attempted to replace it by an extension method on IEnumerable, which of course does not work: Linq to Entities needs an Expression to parse and translate.
So I created this method:
public static System.Linq.Expressions.Expression<Func<Data.Lead, bool>> ContainingProductEx(string productName)
{
var ignoreCase = StringComparison.CurrentCultureIgnoreCase;
return (Data.Lead lead) =>
lead.Producten.Any(
(product =>
product.Name.Equals(productName, ignoreCase) ||
product.Parent.Name.Equals(productName, ignoreCase)
));
}
The following selection now works perfectly fine:
var test = db.Leads.Where(Extensions.ContainingProductEx("productA")).ToList();
However, this won't compile, because IGrouping does not contain an override of Where that accepts an Expression:
return (from lead in db.Leads
join postcodeEnProvincie in postcodeEnProvincies
on lead.Postcode equals postcodeEnProvincie.Postcode
where (lead.CreationDate >= range.StartDate) && (lead.CreationDate <= range.EndDate)
group lead by postcodeEnProvincie.Provincie into g
select new Web.Models.GroupedLeads()
{
GroupName = g.Key,
HotLeads = g
.Where(l => l.Type == Data.LeadType.Hot)
.Count(),
Leads = g.Count(),
PriorityLeads = g
.Where(l => l.Type == Data.LeadType.Priority)
.Count(),
Sales = g
.Where(l => l.Sold)
.Count(),
ProductA = g
.Where(Extensions.ContainingProductEx("productA"))
.Count(),
ProductB = g
.Where(Extensions.ContainingProductEx("productB"))
.Count(),
ProductC = g
.Where(Extensions.ContainingProductEx("productC"))
.Count(),
ProductD = g
.Where(Extensions.ContainingProductEx("productD"))
.Count()
}).ToList();
Casting g to IQueryable compiles, but then yields a "Internal .NET Framework Data Provider error 1025.".
Is there any way to wrap this logic in its own method?
This is a problem that can be solved using LINQKit. It allows expressions to be invoked from within other expressions, and it will inline the invoked expression within its caller. Sadly, it only supports a handful of very specific situations, so we'll need to adapt your expression generating method a bit.
Rather than passing the product name to the expression generating method, we'll have it be a parameter of the returned expression:
public static Expression<Func<Data.Lead, string, bool>> ContainingProductEx()
{
var ignoreCase = StringComparison.CurrentCultureIgnoreCase;
return (lead, productName) =>
lead.Producten.Any(
(product =>
product.Name.Equals(productName, ignoreCase) ||
product.Parent.Name.Equals(productName, ignoreCase)
));
}
Next we'll need to call the method before declaring the query:
var predicate = Extensions.ContainingProductEx();
Your query can can now be written as:
from lead in db.Leads.AsExpandable()
//...
ProductA = g
.Where(lead => predicate.Invoke(lead, "productA"))
.Count(),
ProductB = g
.Where(lead => predicate.Invoke(lead, "productB"))
.Count(),
ProductC = g
.Where(lead => predicate.Invoke(lead, "productC"))
.Count(),
ProductD = g
.Where(lead => predicate.Invoke(lead, "productD"))
.Count()
Instead of worrying about creating a function pointer/expression inside your query that you can reference (may not be possible), why not just create a separate private method that takes an IEnumerable<Lead>, a string, and returns an int and reference the method group in your query? I think your confusion is stemming from trying to create an extension method on the collection instead of creating a method that in the collection and the value you are looking for.
Something like:
ProductA = GetLeadsForProduct(g, "productA")
private int GetLeadsForProduct(IEnumerable<Lead> leads, string productType)
{
return leads.Count(l => l.Producten.Any(a => ((a.Name.Equals(productType, StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals(productType, StringComparison.CurrentCultureIgnoreCase)))))
}
I want to pass dynamic lambda expressions to the function below, but I'm not sure how to define the .Take() or .OrderByDescending() on the expression object.
If I want to call the function below, then I want to be able to do this:
dbprovider.Query = (x => x.ConfigurationReference == "172.16.59.175")
.Take(100)
.OrderByDescending(x.Date)
FindEntities(db, dbprovider.Query)
But I can't (this syntax is invalid). Any ideas?
public static List<T> FindEntities<T>(TrackingDataContext dataContext, System.Linq.Expressions.Expression<Func<T, bool>> find) where T : class
{
try
{
var val = dataContext.GetTable<T>().Where(find).ToList<T>();
return val;
}
catch (Exception ex)
{
throw ex;
}
}
The parameter is of type:
System.Linq.Expressions.Expression<Func<T, bool>> find
That means it can take a predicate (the "where" clause), and only a predicate. Thus the only bit you can pass in there is the filter:
x => x.ConfigurationReference == "172.16.59.175"
To do what you want, you would need to add the rest of the code in FindEntities, so that it becomes:
var val = dataContext.GetTable<T>().Where(find)
.OrderByDescending(x => x.Date).Take(100).ToList<T>();
(note also that the Take should really be after the OrderByDescending)
One way you could do that would be:
public static List<T> FindEntities<T>(TrackingDataContext dataContext,
System.Linq.Expressions.Expression<Func<T, bool>> find,
Func<IQueryable<T>, IQueryable<T>> additonalProcessing = null
) where T : class
{
var query = dataContext.GetTable<T>().Where(find);
if(additonalProcessing != null) query = additonalProcessing(query);
return query.ToList<T>();
}
and call:
var data = FindEntities(db, x => x.ConfigurationReference == "172.16.58.175",
q => q.OrderByDescending(x => x.Date).Take(100));
However, frankly I'm not sure what the point of this would be... the caller could do all of that themselves locally more conveniently, without using FindEntities at all. Just:
var data = db.GetTable<T>()
.Where(x => x.ConfigurationReference == "172.16.58.175")
.OrderByDescending(x => x.Date).Take(100).ToList();
or even:
var data = db.SomeTable
.Where(x => x.ConfigurationReference == "172.16.58.175")
.OrderByDescending(x => x.Date).Take(100).ToList();
or just:
var data = (from row in db.SomeTable
where row.ConfigurationReference == "172.16.58.175"
orderby row.Date descending
select row).Take(100).ToList();
I have two tables. Report and ReportData.
ReportData has a constraint ReportID.
How can I write my linq query to return all Report objects where the predicate conditions are met for ReportData? Something like this in SQL:
SELECT * FROM Report as r
Where r.ServiceID = 3 and r.ReportID IN (Select ReportID FROM ReportData WHERE JobID LIKE 'Something%')
This is how I'm building my predicate:
Expression<Func<ReportData, bool>> predicate = PredicateBuilder.True<ReportData>();
predicate = predicate.And(x => x.JobID.StartsWith(QueryConfig.Instance.DataStreamName));
var q = engine.GetReports(predicate, reportsDataContext);
reports = q.ToList();
This is my query construction at the moment:
public override IQueryable<Report> GetReports(Expression<Func<ReportData, bool>> predicate, LLReportsDataContext reportDC)
{
if (reportDC == null) throw new ArgumentNullException("reportDC");
var q = reportDC.ReportDatas.Where(predicate).Where(r => r.ServiceID.Equals(1)).Select(r => r.Report);
return q;
}
I've tried doing the following as well:
public override IQueryable GetReports(Expression> predicate, LLReportsDataContext reportDC)
{
if (reportDC == null) throw new ArgumentNullException("reportDC");
var q = from r in reportDC.Reports
where r.ServiceID.Equals(1)
where r.ReportDatas.Where(predicate.Compile()).Select(x => r.ReportID).Contains(r.ReportID)
select r;
return q;
}
However, I get the this Exception: "Unsupported overload used for query operator 'Where'."
UPDATE
This fixed it:
var q = reportDC.Reports.AsExpandable().
Where(r => r.ReportDatas.Any(predicate.Compile()))
.Where(r => r.ServiceID.Equals(1));
Query
ReportDatas
.Where( reportData => reportData.StartsWith( "Something%" ) &&
reportData.Report.Id ==3)
.Select( reportData => reportData.Report )
.Distinct()
AboutLinqKit
When using LinqKit, sometimes you need to call AsExpandable() in the entity collection and to compile the predicate expression. see this example : ): how-to-use-predicate-builder-with-linq2sql-and-or-operator