var result = await Events.AsQueryable()
.Where(_ => _.Date >= from.Date && _.Date <= to.Date)
.GroupBy(_ => _.SomeProp)
.Select(n => new { n.Key, Count = n.Count() })
.ToListAsync();
works like charm. But lets say that I want to aggregate by custom field - received in aggregateBy string parameter.
So I try:
var propertyToGroupBy = typeof(DiagnosticEvent).GetProperties()
.First(x => x.Name.ToLowerInvariant() == aggregateBy);
And then:
var result = await Events.AsQueryable()
.Where(_ => _.Date >= from.Date && _.Date <= to.Date)
.GroupBy(p => propertyToGroupBy.GetValue(p))
.Select(n => new { n.Key, Count = n.Count() })
.ToListAsync();
Which ends up in:
GetValue of type System.Reflection.PropertyInfo is not supported in
the expression tree System.String Type.GetValue({document})
Any idea how to do it while sticking to LINQ?
I know that i can use Fluent API with:
var group = new BsonDocument { { "_id", $"${aggregateBy}"}, { "Count", new BsonDocument("$sum", 1) } };
But I want to be consistent with LINQ approach.
Full stack trace: https://pastebin.com/RNzXi1kj
Related
I have the following code, where I first create a list object assessmentItems, and then use it inside a LINQ to get the CurrentScore.
List<AssessmentItem> assessmentItems =
_context.AssessmentItems
.Include(ai => ai.Assessment).ThenInclude(assess => assess.Evaluator)
.Where(ai => ai.IsActive &&
ai.Assessment.PeerReviewAssignmentId == peerReviewAssignmentId &&
ai.Assessment.RubricId == rubricId).ToList();
List<RubricDTO> resultToReturn = _context.Rubrics
.Include(r => r.RubricItemCategories)
.Where(r => r.Id == rubricId)
.Select(r => new RubricDTO
{
Ranking = r.Ranking,
Description = r.Description,
RubricItemCategories = r.RubricItemCategories.Select(ric => new RubricItemCategoryDTO
{
Id = ric.Id,
Description = ric.Description,
RubricItems = ric.RubricItems.Select(ri => new RubricItemDTO
{
Id = ri.Id,
Title = ri.Title,
CurrentScore = assessmentItems.Count > 0 ? assessmentItems
.Where(aitem2 => aitem2.RubricItemId == ri.Id && aitem2.Assessment.EvaluatorId == userId)
//.Take(1)
.Select(s => s.CurrentScore)
.FirstOrDefault() : 0,
})
}).OrderBy(ric => ric.Order).ToList()
}).ToList();
However, I receive the following error:
System.InvalidOperationException: The LINQ expression 'aitem2' could
not be translated. Either rewrite the query in a form that can be
translated, or switch to client evaluation explicitly by inserting a
call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or
'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for
more information.
Any ideas why this might be happenning?
Let me hep you to simplify your query. Includes are not needed because you have custom projection. If you add ToList for subquery elements - it will be not translatable. If you need translatable query - work with IQueryable.
var assessmentItems = _context.AssessmentItems
.Where(ai => ai.IsActive &&
ai.Assessment.PeerReviewAssignmentId == peerReviewAssignmentId &&
ai.Assessment.RubricId == rubricId);
var resultToReturn = _context.Rubrics
.Where(r => r.Id == rubricId)
.Select(r => new RubricDTO
{
Ranking = r.Ranking,
Description = r.Description,
RubricItemCategories = r.RubricItemCategories.Select(ric => new RubricItemCategoryDTO
{
Id = ric.Id,
Description = ric.Description,
RubricItems = ric.RubricItems.Select(ri => new RubricItemDTO
{
Id = ri.Id,
Title = ri.Title,
CurrentScore = assessmentItems
.Where(aitem2 => aitem2.RubricItemId == ri.Id && aitem2.Assessment.EvaluatorId == userId)
.Select(s => s.CurrentScore)
.FirstOrDefault(),
})
}).OrderBy(ric => ric.Order).ToList()
}).ToList();
I want to translate this into lambda syntax and can't seem to get it to work:
Grouping by two columns, select max on a different column, return list of complete complex object.
I am writing more text here to get past the validation on this form. How much text is needed until I am allowed to post this?
_clientpolicies = (from policy in
_reply.CommercialInsuredGroupWithPolicyTerm.InsuredWithPolicyTerm.SelectMany(x => x.PolicyTerm)
.Where(x => !(string.IsNullOrWhiteSpace(x.PolicyNumber) && string.IsNullOrWhiteSpace(x.ControlNumber)))
.Where(x => x.Insured.DNBAccountNumber == _client.LookupID)
group policy by
new
{
PolicyReference = GetPolicyReference(policy),
PolicyType = policy.ProductInformation.PolicyTypeCode
}
into g
let maxPolicyInception = g.Max(p => p.InceptionDate)
from policyGroup in g
where policyGroup.InceptionDate == maxPolicyInception
select policyGroup).ToList();
I dont think there's a way of doing it in one line. So there's my try :
policyGroups=
_reply.CommercialInsuredGroupWithPolicyTerm.InsuredWithPolicyTerm
.SelectMany(x => x.PolicyTerm)
.Where(x => !(string.IsNullOrWhiteSpace(x.PolicyNumber) && string.IsNullOrWhiteSpace(x.ControlNumber)))
.Where(x => x.Insured.DNBAccountNumber == _client.LookupID)
.GroupBy(x => GetPolicyReference(x))
.ThenBy(x => x.ProductInformation.PolicyTypeCode)
.ToList();
var maxPolicyInception = policyGroups.Max(p => p.InceptionDate);
_clientpolicies = policyGroups
.Where(g => g.InceptionDate == maxPolicyInception)
.ToList();
_clientpolicies =
_reply.CommercialInsuredGroupWithPolicyTerm.InsuredWithPolicyTerm.SelectMany(x => x.PolicyTerm)
.Where(x => !(string.IsNullOrWhiteSpace(x.PolicyNumber) && string.IsNullOrWhiteSpace(x.ControlNumber)))
.Where(x => x.Insured.DNBAccountNumber == _client.LookupID)
.GroupBy(x =>
new
{
PolicyReference = GetPolicyReference(x),
PolicyType = x.ProductInformation.PolicyTypeCode
},
(key, g) => g.OrderByDescending(gx => gx.InceptionDate).First()
How can i replace x.Demographic.AgeRange with any other field?
Eg
var field_to_check = "Country";
x.Demographic.ReflectedProperty(field_to_check)=="USA"
var field_to_check = "AgeRnage";
x.Demographic.ReflectedProperty(field_to_check)=="20-30"
I Tried with a refelcted property. but cant succeed.
Favourability = db.Questions
.OrderByDescending(x => x.Responces.Count(y => y.Responseval == Constants.options.Agree || y.Responseval == Constants.options.Tend_to_Agree))
.Select(z => new
{
z.QuestionTitle,
Count = z.Responces.Where(x =>
x.Demographic.AgeRange == repval &&
(x.Responseval == Constants.options.Agree || x.Responseval == Constants.options.Tend_to_Agree)
)
.Count()
})
.Select(z => new
{
z.QuestionTitle,
z.Count,
Perc = ((z.Count / totresponcecount) * 100)
}
)
.ToList();
so that i can write only one linq statement as dynamic filtering rather than switch statements for all required properties.
You can construct the expression tree from multiple different ones. Use LinqKit, and write:
Expression<Func<Demography, string>> fieldSpec = d => d.AgeRange;
And then in your expession:
var exp = db.Questions.AsExpadable() ... fieldSpec.Expand(x.Demographic) == repval ...
Now all you need to do is construct the fieldSpec dynamically, which is an excercise for you :)
I have a fairly complicated query that would read from a table, then do group on CONTACT_ID, then select only those group with count of 1.
This query is fairly complicated and I have no idea how to optimize it in LINQ.
var linkTable = this.DB.Links
.Where(l=>l.INSTANCE_ID==123456 && l.CONTACT_ID.HasValue && l.ORGANISATION_ID.HasValue)
.Select(l => new { l.DEFAULT_LINKED_ORGANISATION, l.LINK_ID, l.CONTACT_ID });
var defaultOrganizationLinkQuery = linkTable
.Where(l => l.DEFAULT_LINKED_ORGANISATION)
.Select(l => l.LINK_ID);
var singleOrganizationLinkQuery = linkTable
.GroupBy(l => l.CONTACT_ID)
.Select(group => new
{
CONTACT_ID = group.Key,
contact_link_count = group.Count(),
LINK_ID = group.First().LINK_ID
})
.Where(l => l.contact_link_count == 1)
.Select(l => l.LINK_ID);
var merged = singleOrganizationLinkQuery.Union(defaultOrganizationLinkQuery);
I made shorter version, but I do not expect it to be faster. If it works and is not slower I would be satisfied:
var merged = this.DB.Links
.Where(l=>l.INSTANCE_ID==123456 && l.CONTACT_ID.HasValue && l.ORGANISATION_ID.HasValue)
.GroupBy(l => l.CONTACT_ID)
.SelectMany(s => s.Where(x => s.Count() == 1 || x.DEFAULT_LINKED_ORGANISATION)
.Select(link => link.LINK_ID));
This works fine.g.Key is not null and has appropriate data:
var result = db.JournalEntries.Include(je => je.JournalRecords.Select(jr => jr.Account).Select(j => j.AccountParticulars))
.Where(je => je.Date >= existingLedgerTransaction.From && je.Date <= existingLedgerTransaction.To)
.SelectMany(s => s.JournalRecords)
.GroupBy(d => d.AccountParticular.Account.AccountCategory)
.Select(g => new { Name = g.Key.Name });
But this does not work as g.Key is null:
var DateFilter = new Func<JournalEntry, bool>(je => je.Date >= existingLedgerTransaction.From && je.Date <= existingLedgerTransaction.To);
var result = db.JournalEntries.Include(je => je.JournalRecords.Select(jr => jr.Account).Select(j => j.AccountParticulars))
.Where(DateFilter)
.SelectMany(s => s.JournalRecords)
.GroupBy(d => d.AccountParticular.Account.AccountCategory)
.Select(g => new { Name = g.Key.Name });
I tried the same thing in a simple console app with static collection and passing in predicate works fine. What could be the problem here?
NOTE: Lazy loading/dynamic proxy is disabled
Try
var DateFilter = new Expression<Func<JournalEntry, bool>>(je => je.Date >= existingLedgerTransaction.From && je.Date <= existingLedgerTransaction.To);
as you need to pass an expression tree to EF