Convert IGrouping to IQueryable in LINQ - c#

if (model.ConnectedToOtherProfilesId != 0)
{
var fooGroup = fans.GroupBy(x => x.FanId)
.Where(x => x.Any(z => z.ProfileId == model.ConnectedToOtherProfilesId));
var fooGroup2 = fooGroup.Where(grp => grp.Count() > 1);
}
What I need is to put the results from fooGroup2 [IQueryable<IGrouping<int,PF>] into fans which is IQueryiable<PF>
Something like this:
fans = fooGroup2;

You could use a SelectMany.
//IQueryable<PF>
var fooGroup2 = fooGroup.Where(grp => grp.Count() > 1)
.SelectMany(pf => pf);

Related

Linq Group by with nested collection throwing Cannot perform an aggregate function

I want to do something like this:
var projectHistory = await Context.Tasks.GroupBy(x => x.ProjectId).Select(x => new ProjectHistoryStatModel
{
ProjectId = x.Key,
CompletedTasks = x.Where(y => y.StatusId == 4).Count(),
InProgressTasks = x.Where(y => y.StatusId == 3).Count(),
DelayedTasks = x.Where(y => y.EndDate < DateTime.Now && y.StatusId != 4).Count(),
DependentTasks = x.Where(y => y.Dependents.Any()).Count(),
TotalTasks = x.Count()
}).ToListAsync();
But DependentTasks property DependentTasks = x.Where(y => y.Dependents.Any()).Count(), is throwing:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
Well y.Dependents is a Collection that is why its throwing the problem, I also tried this: DependetTasks = Context.TaskDependencies.Where(y => x.Any(z => z.Id == y.TaskId)).Count(), and it throws the same error.
Can you guys show me a way of doing this in the same request to the DB?
Regards
Assuming that you are using EF Core < 6.0, you can try to rewrite your query in the following way:
var query =
from t in Context.Tasks
group new { t, HasDependents = t.Dependents.Any() } by t.ProjectId into g
select new ProjectHistoryStatModel
{
ProjectId = g.Key,
CompletedTasks = g.Where(y => y.t.StatusId == 4).Count(),
InProgressTasks = g.Where(y => y.t.StatusId == 3).Count(),
DelayedTasks = g.Where(y => y.t.EndDate < DateTime.Now && y.t.StatusId != 4).Count(),
DependentTasks = g.Where(y => y.HasDependents).Count(),
TotalTasks = g.Count()
};
var projectHistory = await query.ToListAsync();
EF Core up to 6.0 do not support translating navigation properties after GroupBy.

LINQ query(Lambda Expressions) and String.Join

How to write a Strng.Join for this type of LINQ query(Lambda expression)? (var b will return number of integer lists)
1.
var b = braughtForwardInvoices
.Where(x => x.LinkedTransaction != null
&& x.LinkedTransaction.Count > 0)
.Select(x => x.LinkedTransaction.Select(x => x.TransactionId))
.ToList();
Below show the actual working query. I need to write the code in number 1 like number 2.
2.
var braughtForwardPaymentId =
from item in braughtForwardInvoices
where item.LinkedTransaction != null
&& item.LinkedTransaction.Count > 0
select string.Join(",", item.LinkedTransaction.Select(x => x.TransactionId)).ToString();
Take the list and send it to string.Join.
var b = braughtForwardInvoices.Where(x => x.LinkedTransaction != null
&& x.LinkedTransaction.Count > 0)
.Select(x => x.LinkedTransaction.Select(x => x.TransactionId))
.ToList();
var result = string.Join(",", b);
If you just need the selected IDs as a CSV string, you can join the resulting IEnumerable:
var b = string.Join(", ", braughtForwardInvoices.Where(x => x.LinkedTransaction != null
&& x.LinkedTransaction.Count > 0)
.Select(x => x.LinkedTransaction.Select(x => x.TransactionId)));

How to add leading 0 to int variable

I have a list which contains instanceNumber properties of type int I want to add leading 0 if its value is less than 10. i.e 01,02...09 after that 10,11,12 and goes on etc. I tried following code but it did not work
var sidList = _sidRepository.GetAllList().Where(q=>q.IsDeleted==false).OrderByDescending(q=>q.Id).ToList();
if (sidList.Count > 0)
{
sidList.Where(w => w.InstanceNumber<10).ToList().
ForEach(s => s.InstanceNumber = s.InstanceNumber.Value.ToString("D").Length + 2);
}
Try this:
var sidList = _sidRepository.GetAllList()
.Where(q => !q.IsDeleted)
.OrderByDescending(q => q.Id)
.ToList();
if (sidList.Any())
{
sidList
.Where(w => w.InstanceNumber < 10)
.ToList()
.ForEach(s => s.InstanceNumber = s.InstanceNumber.Value.ToString("00").Length + 2);
}
What I would suggest:
var sidList = _sidRepository.GetAllList()
.Where(q => !q.IsDeleted)
.OrderByDescending(q => q.Id);
if (sidList.Any())
foreach (var item in sidList.Where(i => i.InstanceNumber < 10))
item.InstanceNumber = item.InstanceNumber.Value.ToString("00").Length + 2;
My suggestion will return a IEnumerable<item> instead of a List<item>, but you can handle it afterwards with a .ToList() if you need it, but you'll benefit from performance, because you'll avoid ToList(ing) two times.

Append index number to duplicated string value in a list - by using Lambda

I have a IList<string>() which holds some string values, and there could be duplicated items in the list. What I want is to append a index number to end of the string to eliminate the duplication.
For example, I have these values in my list: StringA, StringB, StringC, StringA, StringA, StringB. And I want the result looks like: StringA1, StringB1, StringC, StringA2, StringA3, StringB2. I need to retain the original order in list.
Is there a way I can just use one Lambda expression?
You are looking for something like this:
yourList.GroupBy(x => x)
.SelectMany(g => g.Select((x,idx) => g.Count() == 1 ? x : x + idx))
.ToList();
Edit: If the element order matters, here is another solution:
var counts = yourList.GroupBy(x => x).ToDictionary(x => x.Key, x => x.Count());
var values = counts.ToDictionary(x => x.Key, x => 0);
var list = yourList.Select(x => counts[x] > 1 ? x + ++values[x] : x).ToList();
You can do:
List<string> list = new List<string> { "StringA", "StringB", "StringC", "StringA", "StringA", "StringB" };
var newList =
list.Select((r, i) => new { Value = r, Index = i })
.GroupBy(r => r.Value)
.Select(grp => grp.Count() > 1 ?
grp.Select((subItem, i) => new
{
Value = subItem.Value + (i + 1),
OriginalIndex = subItem.Index
})
: grp.Select(subItem => new
{
Value = subItem.Value,
OriginalIndex = subItem.Index
}))
.SelectMany(r => r)
.OrderBy(r => r.OriginalIndex)
.Select(r => r.Value)
.ToList();
and you will get:
StringA1,StringB1,StringC,StringA2,StringA3,StringB2
If you don't want to preserve order then you can do:
var newList = list.GroupBy(r => r)
.Select(grp => grp.Count() > 1 ?
grp.Select((subItem, i) => subItem + (i + 1))
: grp.Select(subItem => subItem))
.SelectMany(r => r)
.ToList();
This uses some lambda expressions and linq to do it, maintaining the order but I'd suggested a function with a foreach loop and yield return would be better.
var result = list.Aggregate(
new List<KeyValuePair<string, int>>(),
(cache, s) =>
{
var last = cache.Reverse().FirstOrDefault(p => p.Key == s);
if (last == null)
{
cache.Add(new KeyValuePair<string, int>(s, 0));
}
else
{
if (last.Value = 0)
{
last.Value = 1;
}
cache.Add(new KeyValuePair<string, int>(s, last.Value + 1));
}
return cache;
},
cache => cache.Select(p => p.Value == 0 ?
p.Key :
p.Key + p.Value.ToString()));

Add correlated subqueries to Select statement using QueryOver

I'm attempting to convert a SQL statement to use QueryOver (in hopes of pre-fetching the entities part of the response) but I'm having trouble figuring out how to add a correlated subquery to the Select statement (all the examples I found have only shown using a subquery in the Where clause).
This is the query I'm trying to convert:
var pendingFeedbackStatus = Session.QueryOver<FeedbackStatus>().Where(fs => fs.Name == "pending");
var projectWhereClause = project != null ? "AND f1.project_id = " + project.Id : "";
var query = Session.CreateSQLQuery(string.Format(#"
SELECT
ft.id as FEEDBACK_TYPE_ID,
(SELECT COUNT(*) FROM FEEDBACK f1 WHERE ft.id = f1.feedback_type_id AND f1.archive_ind = 0 {0}) as ALL_FEEDBACK_COUNT,
(SELECT COUNT(*) FROM FEEDBACK f1 WHERE ft.id = f1.feedback_type_id AND f1.archive_ind = 0 {0} AND feedback_status_id = {1}) as PENDING_FEEDBACK_COUNT
FROM feedback f
RIGHT JOIN feedback_type ft on f.feedback_type_id = ft.id WHERE ft.RESTRICTED_IND = 0
GROUP BY ft.id, ft.sort_order
ORDER BY ft.sort_order",
projectWhereClause,
pendingFeedbackStatus.Id
))
.SetResultTransformer(Transformers.AliasToEntityMap);
var results = query.List<IDictionary>();
return results.Select(r =>
new FeedbackTypeSummary
{
Type = Get(Convert.ToInt32(r["FEEDBACK_TYPE_ID"])),
AllFeedbackCount = Convert.ToInt32(r["ALL_FEEDBACK_COUNT"]),
PendingFeedbackCount = Convert.ToInt32(r["PENDING_FEEDBACK_COUNT"])
}).ToList();
and here is what I have so far (which is mostly everything minus the correlated subqueries and some additional filtering added to the subqueries):
var pendingFeedbackStatus = Session.QueryOver<FeedbackStatus>().Where(fs => fs.Name == "pending");
Feedback feedbackAlias = null;
FeedbackType feedbackTypeAlias = null;
var allFeedback = QueryOver.Of<Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived);
var pendingFeedback = QueryOver.Of<Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived)
.Where(f => f.Status.Id == pendingFeedbackStatus.Id);
var foo = Session.QueryOver<Feedback>(() => feedbackAlias)
.Right.JoinAlias(f => f.Type, () => feedbackTypeAlias, ft => !ft.IsRestricted)
.SelectList(list => list
// TODO: Add correlated subqueries here?
.SelectGroup(() => feedbackTypeAlias.Id)
.SelectGroup(() => feedbackTypeAlias.SortOrder)
)
.OrderBy(() => feedbackTypeAlias.SortOrder).Asc;
var test = foo.List<object[]>();
I'd also like to find a way to return a full FeedbackType entity of from the statement, instead of returning feedbackTypeAlias.Id and then having to perform Type = Get(Convert.ToInt32(r["FEEDBACK_TYPE_ID"])) in a loop as I do in the original.
I felt like I looked for this 10 times, but I overlooked the .SelectSubQuery() method which provided the desired correlated subqueries. This answer tipped me off - https://stackoverflow.com/a/8143684/191902.
Here is the full QueryOvery version:
var pendingFeedbackStatus = Session.QueryOver<FeedbackStatus>().Where(fs => fs.Name == "pending").SingleOrDefault();
Domain.Feedback.Feedback feedbackAlias = null;
FeedbackType feedbackTypeAlias = null;
var allFeedback = QueryOver.Of<Domain.Feedback.Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived);
var pendingFeedback = QueryOver.Of<Domain.Feedback.Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived)
.Where(f => f.Status.Id == pendingFeedbackStatus.Id);
if (project != null)
{
allFeedback.Where(f => f.Project.Id == project.Id);
pendingFeedback.Where(f => f.Project.Id == project.Id);
}
FeedbackTypeSummary result = null;
var query = Session.QueryOver<Domain.Feedback.Feedback>(() => feedbackAlias)
.Right.JoinAlias(f => f.Type, () => feedbackTypeAlias, ft => !ft.IsRestricted)
.SelectList(list => list
.SelectSubQuery(allFeedback.ToRowCountQuery()).WithAlias(() => result.AllFeedbackCount)
.SelectSubQuery(pendingFeedback.ToRowCountQuery()).WithAlias(() => result.PendingFeedbackCount)
.SelectGroup(() => feedbackTypeAlias.Id).WithAlias(() => result.TypeId)
.SelectGroup(() => feedbackTypeAlias.Name).WithAlias(() => result.TypeName)
.SelectGroup(() => feedbackTypeAlias.NamePlural).WithAlias(() => result.TypeNamePlural)
.SelectGroup(() => feedbackTypeAlias.SortOrder)
)
.OrderBy(() => feedbackTypeAlias.SortOrder).Asc
.TransformUsing(Transformers.AliasToBean<FeedbackTypeSummary>());
var results = query.List<FeedbackTypeSummary>();
return results;
I also was able to populate my FeedbackTypeSummary DTO from a single query, although I couldn't find a way to alias an entity and ended up extracting a few of the needed properties from FeedbackType into FeedackTypeSummary (which is probably a better thing to do anyways).

Categories