LINQ refactoring help needed - linq

How would you refactor this code, with respect to the LINQ? I'm new to LINQ and haven't quite have a good handle on more complex queries (nested, grouping).
Can all of these three statements and foreach loop been converted into one LINQ statement?
void AddSeries(Series series, int phraseId)
{
using (var db = Database.Instance)
{
foreach (var date in db.Ad.Select(ad => ad.DateTime.Date).Distinct())
{
var phraseCount = (from pc in db.PhraseCount
where pc.DateTime.Date == date &&
pc.PhraseId == phraseId
select pc.Count).SingleOrDefault();
var adCount = db.Ad.Where(ad => ad.DateTime.Date == date).Count();
series.Add(date, phraseCount / adCount);
}
}
}

Here's my first shot. Hard without having your model.
var q = from ad in db.Ad
group ad by ad.DateTime.Date into g
select new
{
AdCount = g.Count(),
Date = g.Key,
PhraseCount = (from pc in db.PhraseCount
where pc.DateTime.Date == g.Key
&& pc.PhraseId == phraseId
select pc).Count()
}

Related

EF Core : expression trees - GroupJoin transform to SelectMany dynamically

I would like to transform the below query:
(from documentType in entitySet
join userGroupId in Repository.ConvertToBigIntTable(userGroupIds, "userGroupId")
on documentType.Id equals userGroupId.Id into UserGroupIds
from userGroupId in UserGroupIds.DefaultIfEmpty()
join documentTypePermission in Repository.DocumentTypePermissions
on documentType.Id equals documentTypePermission.DocumentTypeId
join userSelectionParam in Repository.UserSelectionParams
on documentTypePermission.UserSelectionId equals userSelectionParam.Id
where documentTypePermission.IsActive &&
((userSelectionParam.UserGroupId != null && userGroupId.Id != null)
|| (userSelectionParam.UserId != null && userSelectionParam.UserId == CurrentUserId))
select documentTypePermission);
to something like this:
var query =
from documentType in entitySet
from userGroupId in Repository.ConvertToBigIntTable(userGroupIds, "userGroupId")
.Where(userGroupId => documentType.Id == userGroupId.Id)
.DefaultIfEmpty()
join documentTypePermission in Repository.DocumentTypePermissions
on documentType.Id equals documentTypePermission.DocumentTypeId
join userSelectionParam in Repository.UserSelectionParams
on documentTypePermission.UserSelectionId equals userSelectionParam.Id
....
Note:
I have the setup ready for intercepting the expression tree evaluation using QueryTranslationPreprocessor
I need the logic to generate the output of .Call, .SelectMany etc by using the first linq and then translating the same to second linq query shown above
The caveats are that internally the expression engine generates so many Anonymous types that I am unable to write a generic code that would suffice different cases of linq with such groupjoins
Reason to do this:
There are several kinds of GroupJoin issues in EF Core 6 which the community isn’t ready to resolve
I can't make the change to Linq query directly as there are 1000s of them and in several places
So after evaluating multiple options, the best way to do the above is as below at VisitMethodCall(MethodCallExpression node)
if (node.Method.DeclaringType == typeof(Queryable) && node.Method.Name == nameof(Queryable.GroupJoin) && node.Arguments.Count == 5)
{
var outer = Visit(node.Arguments[0]);
var inner = Visit(node.Arguments[1]);
var outerKeySelector = Visit(node.Arguments[2]).UnwrapLambdaFromQuote();
var innerKeySelector = Visit(node.Arguments[3]).UnwrapLambdaFromQuote();
var resultSelector = Visit(node.Arguments[4]).UnwrapLambdaFromQuote();
var outerKey = outerKeySelector.Body.ReplaceParameter(outerKeySelector.Parameters[0], resultSelector.Parameters[0]);
var innerKey = innerKeySelector.Body;
var keyMatch = MatchKeys(outerKey, innerKey);
var innerQuery = Expression.Call(
typeof(Queryable), nameof(Queryable.Where), new[] { innerKeySelector.Parameters[0].Type },
inner, Expression.Lambda(keyMatch, innerKeySelector.Parameters));
var asEnumerableInnerQuery = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.AsEnumerable),
new Type[] { innerKeySelector.Parameters[0].Type }, innerQuery);
var resultTypes = resultSelector.Parameters.Select(p => p.Type).ToArray();
var tempProjectionType = typeof(Tuple<,>).MakeGenericType(resultTypes);
var tempProjection = Expression.New(
tempProjectionType.GetConstructor(resultTypes),
new Expression[] { resultSelector.Parameters[0], asEnumerableInnerQuery },
tempProjectionType.GetProperty("Item1"), tempProjectionType.GetProperty("Item2"));
var tempQuery = Expression.Call(
typeof(Queryable), nameof(Queryable.Select), new[] { outerKeySelector.Parameters[0].Type, tempProjectionType },
outer, Expression.Lambda(tempProjection, resultSelector.Parameters[0]));
var tempResult = Expression.Parameter(tempProjectionType, "p");
var projection = resultSelector.Body
.ReplaceParameter(resultSelector.Parameters[0], Expression.Property(tempResult, "Item1"))
.ReplaceParameter(resultSelector.Parameters[1], Expression.Property(tempResult, "Item2"));
var query = Expression.Call(
typeof(Queryable), nameof(Queryable.Select), new[] { tempProjectionType, projection.Type },
tempQuery, Expression.Lambda(projection, tempResult));
return query;
}
return base.VisitMethodCall(node);
}

Null substitution by LINQ

Sorry for the very rudimentary question.
I used LINQ to join the two tables.
using(var db = new BooksDbContext())
{
var q = db.Books.Join(db.Author,
b => b.Id,
a => a.Id,
(b, a) => new { Book = b, Author = a });
}
From the obtained result, I would like to fill the null part with 0.
I want to leave non-null values ​​as they are.
If you write it without using LINQ, you can imagine this.
0 if null.
If it is not null, the value is stored as it is.
while (reader.Read())
{
for(var i = 0; i <= 10; i++)
{
if (Convert.IsDBNull(reader[i]))
{
testList[i] = 0;
}
else testList[i] = reader[i]
}
}
I want to do this with LINQ, but I'm having a hard time thinking about it.
I would like advice.
Sorry for the boring question.

Code Rewite for tuple and if else statements by using LINQ

In my C# application i am using linq. I need a help what is the syntax for if-elseif- using linq in single line. Data, RangeDate are the inputs. Here is the code:
var Date1 = RangeData.ToList();
int record =0;
foreach (var tr in Date1)
{
int id =0;
if (tr.Item1 != null && tr.Item1.port != null)
{
id = tr.Item1.port.id;
}
else if (tr.Item2 != null && tr.Item2.port != null)
{
id = tr.Item2.port.id;
}
if (id >0)
{
if(Data.Trygetvalue(id, out cdat)
{
// Do some operation. (var cdata = SumData(id, tr.item2.port.Date)
record ++;
}
}
}
I think your code example is false, your record variable is initialized to 0 on each loop so increment it is useless .
I suppose that you want to count records in your list which have an id, you can achieve this with one single Count() :
var record = Date1.Count(o => (o.Item1?.port?.id ?? o.Item2?.port?.id) > 0);
You can use following code:
var count = RangeData.Select(x => new { Id = x.Item1?.port?.id ?? x.Item2?.port?.id ?? 0, Item = x })
.Count(x =>
{
int? cdate = null; // change int to your desired type over here
if (x.Id > 0 && Data.Trygetvalue(x.Id, out cdat))
{
// Do some operation. (var cdata = SumData(x.Id, x.Item.Item2.port.Date)
return true;
}
return false;
});
Edit:
#D Stanley is completely right, LINQ is wrong tool over here. You can refactor few bits of your code though:
var Date1 = RangeData.ToList();
int record =0;
foreach (var tr in Date1)
{
int? cdat = null; // change int to your desired type over here
int id = tr.Item1?.port?.id ?? tr.Item2?.port?.id ?? 0;
if (id >0 && Data.Trygetvalue(id, out cdat))
{
// Do some operation. (var cdata = SumData(id, tr.Item2.port.Date)
record ++;
}
}
Linq is not the right tool here. Linq is for converting or querying a collection. You are looping over a collection and "doing some operation". Depending on what that operation is, trying to shoehorn it into a Linq statement will be harder to understand to an outside reader, difficult to debug, and hard to maintain.
There is absolutely nothing wrong with the loop that you have. As you can tell from the other answers, it's difficult to wedge all of the information you have into a "single-line" statement just to use Linq.

Linq filter after join

I'm joining 4 different lists together in linq. I need to then do a filter before the select new. I'm having trouble figuring this out.
var results2 = from st in this
join currentGrowth in levels on new {st.Core, st.Grade } equals new {currentGrowth.Core, currentGrowth.Grade } into currentLevel
from level in currentLevel.Where(a => st.Score >= a.MinScore && st.Score <= a.MaxScore).DefaultIfEmpty()
join growthScores in ScoresGrowth on new { st.id, st.Core } equals new { growthScores.id, growthScores.Core } into gs
from gscores in gs.Where(a => !a.Passed && !st.Passed && a.TestName != st.TestName).DefaultIfEmpty()
join pastGrowth in levels on new { gscores?.Core, gscores?.Grade } equals new { pastGrowth?.Core, pastGrowth?.Grade } into pLevel
from pastLevel in pLevel.Where(a => gscores.Score >= a.MinScore && gscores.Score <= a.MaxScore).DefaultIfEmpty()
group new { st, gscores, level, pastLevel } by new { st.Division, st.School, st.Core }
into testGroups
select new
{
Division = testGroups.Key.Division,
Core = testGroups.Key.Core,
School = testGroups.Key.School,
All_Total = testGroups.Count(),
All_PassCount = testGroups.Count(a => a.st.Passed),
All_Growth = testGroups.Count(a=> !a.st.Passed && (a.level?.Level > a.pastLevel?.Level) )
};
I need the following filter applied to the fields retrieved in the select. Is there a way to do this before the select new, or must it be applied to every calculation in the select new. There are many more than are included here.
!(st.SOA_LEP && !st.Passed && pastLevel.Level < level.Level)
&& !(st.SOA_Transfer && !st.Passed && pastLevel.Level < level.Level)

LINQ: Group By + Where in clause

I'm trying to implement a T-SQL equivalent of a where in (select ...) code in LINQ.
This is what I have now:
int contactID = GetContactID();
IEnumerable<string> threadList = (from s in pdc.Messages
where s.ContactID == contactID
group 1 by new { s.ThreadID } into d
select new { ThreadID = d.Key.ThreadID}).ToList<string>();
var result = from s in pdc.Messages
where threadList.Contains(s.ThreadID)
group new { s } by new { s.ThreadID } into d
let maxMsgID = d.Where(x => x.s.ContactID != contactID).Max(x => x.s.MessageID)
select new {
LastMessage = d.Where(x => x.s.MessageID == maxMsgID).SingleOrDefault().s
};
However, my code won't compile due to this error for the ToList():
cannot convert from
'System.Linq.IQueryable<AnonymousType#1>'
to
'System.Collections.Generic.IEnumerable<string>'
Anyone have any suggestions on how to implement this? Or any suggestions on how to simplify this code?
Your query returns a set of anonymous types; you cannot implicitly convert it to a List<string>.
Instead, you should select the string itself. You don't need any anonymous types.
Change it to
var threadList = pdc.Messages.Where(s => s.ContactID == contactID)
.Select(s => s.ThreadID)
.Distinct()
.ToList();
var result = from s in pdc.Messages
where threadList.Contains(s.ThreadID)
group s by s.ThreadID into d
let maxMsgID = d.Where(x => x.ContactID != contactID).Max(x => x.MessageID)
select new {
LastMessage = d.Where(x => x.MessageID == maxMsgID).SingleOrDefault()
};

Resources