Linq-To-SQL Marc Gravell's InRange Extension - c#

I'm tring to use the InRange extension Marc Gravell wrote here:
LINQ Expression to return Property value?
This is my code so far:
// Fetch list of visit Ids and distinct Ips that fall into the date range
var q = (from c in db.tblTrackerVisits where c.Date >= MinDate select new { c.ID, c.IPID });
List<int> VisitIDs = q.Select(c => c.ID).ToList();
List<int> DistinctIPs = q.Select(c => c.IPID).Distinct().ToList();
// List of all campaigns that have visitors
var Campaigns = db.tblTrackerVariables
.Where(c =>
c.TypeID == Settings.CampaignTrackerVariableTypeID
//&& db.tblTrackerVisitVariables.Any(d=>VisitIDs.Contains(d.VisitID) && d.VariableID == c.ID)
&& db.tblTrackerVisitVariables.InRange(x => x.VisitID, 1500, VisitIDs)
)
.Select(c => new { c.ID, c.Name }).OrderBy(c=>c.Name);
However this throws:
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<tblTrackerVisitVariable>' to 'bool'
I'm not sure if I'm using it correctly, can anyone give me some pointers? The commented out && above it:
//&& db.tblTrackerVisitVariables.Any(d=>VisitIDs.Contains(d.VisitID) && d.VariableID == c.ID)
Is the old working code (but it throws the too many params error so I have to resort to this extension method).

Mark's extension method returns an IEnumerable, not a bool, and therefore cannot be included in a logical expression. I you want to check if the result is non-empty try the Any method:
db.tblTrackerVisitVariables.InRange(x => x.VisitID, 1500, VisitIDs).Any()

Related

C# db query where conditions are met, orderby date and then get the first result

While evaluating some queries we found some possible optimization. The ideia is shown below but I currently don't know how to solve this.
Current query:
public static List<Object> SampleQuerySales(int store_id)
{
var query = (from clients in db.table1.Where(p => p.store_id == store_id)
from sales in db.table2.Where(q => q.customer_id == clients.customer_id))
select new Object {
...
}).ToList();
return query;
}
This returns all sales made, but its required only the latest sale (OperationDate) from a datetime reference. As obvious this became a bottleneck.
My ideia was to make it similar to query below, which is incorrect (doesn't compile). How can I achieve this dataset?
var query = (from clients in db.table1.Where(p => p.store_id == store_id)
from sales in db.table2.Where(q => q.customer_id == clients.customer_id
&& q.OperationDate <= dateReference)
.OrderByDescending(s => s.OperationDate).FirstOrDefault() //error
select new Object {
...
}).Tolist();
Since you only want one value from table2, use let instead of from:
var query = (from client in db.table1.Where(p => p.store_id == store_id)
let mostRecentSaleAfterDateReference = db.table2
.Where(q => q.customer_id == client.customer_id
&& q.OperationDate <= dateReference)
.OrderByDescending(s => s.OperationDate)
.FirstOrDefault()
select new Object {
...
}).Tolist();

The LINQ expression 'Expression' could not be translated. Either rewrite the query in a form that can be translated

I have looked at a lot of similar questions but none could give me a solution so I am thinking if anyone can help me with this problem. I have a hierarchy of entities as Clients have multiple ClientRateDeals and then I am trying to fetch only those clients that have a list of client rate deals that all pass some condition. Here's my LINQ query that generating an error :
var query = _context.Client.Where(c=>c.Disabled==false)
.GroupJoin(_context.ClientRateDeal.Where(crd=>crd.Disabled==false),
c => c.Id,
crd => crd.ClientId,
(c, crd) => new
{
c,
crd = crd.Where(cr => cr.DateEnd == null || cr.DateEnd > DateTime.Today)
})
.Where(res => res.crd.Count() == 0)
.Select(cl => cl.c).AsNoTracking().ToList();
as you can see in the result selector argument I have kept that condition and then a where clause on the result selector to fetch only those whose client rate deal whose count is 0. However due to some reason I am getting the exception that the LINQ cant be translated. Can anyone help me with this ?
For unknown reason (it has nothing in similar with GroupBy), LINQ GroupJoin operator is not supported in EF Core 3.x, 5.x.
You have to use one of the available alternatives - (1) collection navigation property (preferred) or (2) correlated subquery.
e.g.
(1) In Client class define
public ICollection<ClientRateDeal> ClientRateDeals { get; set; }
and use it inside the query
var query = _context.Client
.Where(c => c.Disabled==false)
// vvv
.Where(c => !c.ClientRateDeals.Any(
crd => crd.Disabled == false &&
(crd.DateEnd == null || crd.DateEnd > DateTime.Today)))
.AsNoTracking().ToList();
or (2)
var query = _context.Client
.Where(c => c.Disabled==false)
// vvv
.Where(c => !_context.ClientRateDeal.Any(crd =>
c.Id == crd.ClientId &&
crd.Disabled == false &&
cr.DateEnd == null || cr.DateEnd > DateTime.Today))
.AsNoTracking().ToList();
In general, instead of
db.As.GroupJoin(db.Bs, a => a.Id, b => b.AId, (a, Bs) => new { a, Bs })
use
db.As.Select(a => new { a, Bs = db.Bs.Where(b => a.Id == b.AId) })
Related github issue (please go vote in order to have a chance to get that implemented):
Query with GroupBy or GroupJoin throws exception #17068
Query: Support GroupJoin when it is final query operator #19930
even though the second is not exactly what we need (we want just GroupJoin to be translated as it was written in correlated subquery syntax shown above).

I can not make the correct linq request

I form a request, it works.
var query = db.Persons.Where(p => p.Date == "27.02.2020").Where(p => p.Country == "USA");
But since I will need to use it in if-else
I changed it a little and now he just selects the last Where.
How can this problem be solved?
var query = db.Persons;
query.Where(p => p.Date == "27.02.2020");
query.Where(p => p.Country == "USA");
The Where method, like all LINQ methods, does not modify the source enumerable in-place, but instead returns a new enumerable that has the operation, or in this case filter, applied to it.
That means you need to assign the result of each operation back into your variable for it to persist. The following should be equivalent to your original snippet:
IQueryable<Person> query = db.Persons;
query = query.Where(p => p.Date == "27.02.2020");
query = query.Where(p => p.Country == "USA");
As pointed out in the comments, the query variable type now needs to be compatible with both the type of db.Persons and the return type of Where.

Fluent nHibernate Query => QueryOver, Join, Distinct

I try to change that query to QueryOver<> to be able to do the Distinct operation yet inside the (generated sql) query
var result = (from x in Session.Query<Events>()
join o in Session.Query<Receivers>() on x.ID equals o.ID
where x.Owner.ID == 1 //the user is the owner of that Event (not null)
||
x.EVType.ID == 123 //(not null)
||
x.Receivers.Count(y => y.User.ID == 1) > 0 //the user is one of the Event Receivers
select x.StartDate)
.Distinct();
I tried something like that
Events x = null;
List<Receivers> t = null;
var result = Session.QueryOver<Events>(() => x)
.JoinAlias(() => x.Receivers, () => t)
.Where(() => x.Owner.ID == 1
||
x.EVType.ID == 123
||
t.Count(y => y.User.ID == 1) > 0)
.TransformUsing(Transformers.DistinctRootEntity)
.Select(a => a.StartDate)
.List();
but then I got the Value can not be null. Parameter name: source exception. Any ideas how can I fix that query ?
edit
thanks to the xanatos' answer, the final SQL query is correct (I used his 2nd approach):
SELECT distinct this_.StartDate as y0_
FROM Events this_
WHERE
(
this_.UserID = ?
or
this_.EventTypeID = ?
or
exists (SELECT this_0_.ID as y0_
FROM Receivers this_0_
WHERE this_0_.UserID = ?)
)
"In QueryOver, aliases are assigned using an empty variable. The variable can be declared anywhere (but should be empty/default at runtime). The compiler can then check the syntax against the variable is used correctly, but at runtime the variable is not evaluated (it's just used as a placeholder for the alias)." http://nhibernate.info/blog/2009/12/17/queryover-in-nh-3-0.html
Setting List<Receivers> t to empty collection as you did (as you have mentioned in comments) means that you check is event id in local empty collection - doesn't have sense at all.
You can try do your query with subquery (should work but i'm not sure, I wrote it without testing, "by hand"):
Receivers receiversSubQueryAlias = null;
var subquery = session.QueryOver<Events>()
.JoinQueryOver<Receivers>(x => x.Receivers, () => receiversSubqueryAlias, JoinType.Inner)
.Where(()=> receiversSubQueryAlias.UserId == 1)
.Select(x => x.Id)
.TransformUsing(Transformers.DistinctRootEntity);
Events eventsAlias = null;
var mainQueryResults = session.QueryOver<Events>(() => eventsAilas)
.Where(Restrictions.Disjunction()
.Add(() => eventAlias.OwnerId == 1)
.Add(() => eventAlias.EVType.Id == 123)
.Add(Subqueries.WhereProperty<Events>(() => eventAlias.Id).In(subquery))
).Select(x => x.StartDate)
.TransformUsing(Transformers.DistinctRootEntity)
.List();
As written by #fex, you can't simply do a new List<Receivers>. The problem is that you can't mix QueryOver with "LINQ" (the t.Count(...) part). The QueryOver "parser" tries to execute "locally" the t.Count(...) instead of executing it in SQL.
As written by someone else, TransformUsing(Transformers.DistinctRootEntity) is client-side. If you want to do a DISTINCT server-side you have to use Projections.Distinct .
You have to make an explicit subquery. Here there are two variants of the query. the first one is more similar to the LINQ query, the second one doesn't use the Count but uses the Exist (in LINQ you could have done the same by changing the Count(...) > 0 with a Any(...)
Note that when you use a .Select() you normally have to explicitly tell the NHibernate the type of the .List<something>()
Events x = null;
Receivers t = null;
// Similar to LINQ, with COUNT
var subquery2 = QueryOver.Of<Receivers>(() => t)
.Where(() => t.SOMETHING == x.SOMETHING) // The JOIN clause between Receivers and Events
.ToRowCountQuery();
var result2 = Session.QueryOver<Events>(() => x)
.Where(Restrictions.Disjunction()
.Add(() => x.Owner.ID == 1)
.Add(() => x.EVType.ID == 123)
.Add(Subqueries.WhereValue(0).Lt(subquery2))
)
.Select(Projections.Distinct(Projections.Property(() => x.StartDate)))
.List<DateTime>();
// With EXIST
var subquery = QueryOver.Of<Receivers>(() => t)
.Where(() => t.SOMETHING == x.SOMETHING) // The JOIN clause between Receivers and Events
.Select(t1 => t1.ID);
var result = Session.QueryOver<Events>(() => x)
.Where(Restrictions.Disjunction()
.Add(() => x.Owner.ID == 1)
.Add(() => x.EVType.ID == 123)
.Add(Subqueries.WhereExists(subquery))
)
.Select(Projections.Distinct(Projections.Property(() => x.StartDate)))
.List<DateTime>();
Note that you'll have to set "manually" the JOIN condition in the subquery.
Hopefully this answer can help others. This error was being caused by declaring
List<Receivers> t = null;
followed by the query expression
t.Count(y => y.User.ID == 1) > 0
The QueryOver documentation states "The variable can be declared anywhere (but should be empty/default at runtime)." Since in this case, the place holder is a List, you must initialize it as an empty list.
List<Receivers> t = new List<Receivers>();
Otherwise, when you try to reference the Count method, or any other method on the placeholder object, the source (t) will be null.
This however still leaves a problem as #fex and #xanatos, in which it makes no sense to reference Count() from the alias List t, as it won't convert into SQL. Instead you should be creating a subquery. See their answers for more comprehensive answer.

Linq Where/OrderBy string to int

I have seen similar questions on here but none of the answers are working for my linq query.
I am trying to convert a string to integer on the .ThenBy()
dbResults = gaResultDetails.All
.Where(c => c.ContentLink.Id == contentId && c.RequestType.Id == requestTypeId)
.OrderBy(c => c.DateFrom)
.ThenBy(c => int.Parse(c.Data_2)).Take(Take).ToList();
Please note I am using nHibernate for data access and with the above expression get the following error:
[NotSupportedException: Int32 Parse(System.String)]
Help!
Some functions are not supported by the nhibernate linq expression builder, try this:
dbResults = gaResultDetails.All
.Where(c => c.ContentLink.Id == contentId && c.RequestType.Id == requestTypeId)
.AsEnumerable()
.OrderBy(c => c.DateFrom)
.ThenBy(c => int.Parse(c.Data_2))
.Take(Take)
.ToList();
Might not be ideal performance-wise, but should accomplish what you need.
This is just a shot in the dark. If the parse doesn't work in the ThenBy, it probably won't in the let but it's worth a shot. In LINQ syntax, cuz I like it better:
dbResults = (from c in gaResultDetails.All
where c.ContentLink.Id == contentId
&& c.RequestType.Id == requestTypeId
let nData2 = int.Parse(c.Data_2)
orderby c.DateFrom, nData2)
.Take(Take)
.ToList();
It seems like your ORM tries to perform casting on the SQL server side.
Try to evaluate data before casting, e.g. :
dbResults = gaResultDetails.All
.Where(c => c.ContentLink.Id == contentId && c.RequestType.Id == requestTypeId)
.OrderBy(c => c.DateFrom).ToList()
.ThenBy(c => int.Parse(c.Data_2)).Take(Take).ToList();

Categories

Resources