When I write the following code I get the error:
the type argument for method Enumerable.SelectMany cannot be inferred from usage
var model = new Overview()
{
ModelData = data.GroupBy(g => g.GroupingId1).Select(s => new OverviewdataGrouped()
{
Id = s.Key,
Grouping = s.GroupBy(gr => gr.GroupingId2). Select(se => new OverviewdataGroupedFurther()
{
Id= se.Key,
Grouping2 = se.Any() ? se.SelectMany(sel => sel).ToList() : new List<DataModel>()
})
})
};
As far as I know this is how I always selected the data from an IGrouping, but for some reason it is not working this way. Does anyone know what I am missing or what the problem could be?
(Note that the variable sel within the SelectMany contains the correct type (DataModel))
The SelectMany Method projects each element of a sequence to an IEnumerable and flattens the resulting sequences into one sequence.
Your usage of the SelectMany method seems redundant, since se is the result of a grouping operation. Try replacing this:
se.SelectMany(sel => sel).ToList()
By This:
se.ToList()
Related
I am facing a problem while trying to run an aggregation pipeline using MongoDB .NET client. My code looks like so:
public async Task<IEnumerable<string>> GetPopularTags(int count)
{
var events = _database.GetCollection<Event>(_eventsCollectionName);
var agg = events.Aggregate();
var unwind = agg.Unwind<Event, Event>(e => e.Tags);
var group = unwind.Group(e => e.Tags, v => new { Tag = v.Key, Count = v.Count() });
var sort = group.SortByDescending(e => e.Count);
var project = group.Project(r => r.Tag);
var limit = project.Limit(count);
var result = await limit.SingleOrDefaultAsync();
return result;
}
(separate vars for each stage are just for debugging purposes)
While trying to get the result of the pipeline (last var) I get a following error:
System.InvalidCastException: Unable to cast object of type 'MongoDB.Bson.BsonString' to type 'MongoDB.Bson.BsonDocument'
What am I missing?
Thanks in advance for any help!
SOLUTION
I finally figured out that the fact that I was getting an exception at the last line had nothing to do with where the error was. I tried running .SingleOrDefault() on every stage to see outputs and I noticed that my pipeline had a couple of issues.
My unwind stage was trying to return an Event object, but since it was unwinding Tags property (which was a List<string>), it was trying to set it to string and was throwing an exception. I solved that issue by letting it set an output type to the default of BsonDocument and then in next stage using ["Tags"] accessor to get the value I need. It looked something like this:
var dbResult = await events.Aggregate()
.Unwind(e => e.Tags)
.Group(e => e["Tags"], v => new { Tag = v.Key, Count = v.Count() })
My project stage was not working for some reason. I was not able to get the Tag property (which turned out to be a BsonValue type) to be converted to string. In the end I deleted that stage and replaced it with a dbResult.Select(t => t.Tag.AsString) to cast it to a string. Not the most elegant solution, but better than nothing.
In the end my code ended up looking like so:
public async Task<IEnumerable<string>> GetPopularTags(int count)
{
var events = _database.GetCollection<Event>(_eventsCollectionName);
var dbResult = await events.Aggregate()
.Unwind(e => e.Tags)
.Group(e => e["Tags"], v => new { Tag = v.Key, Count = v.Count() })
.SortByDescending(e => e.Count)
.Limit(count)
.ToListAsync();
var result = dbResult.Select(t => t.Tag.AsString);
return result;
}
The problem you're facing can be basically simplified to below line of code:
var agg = collection.Aggregate().Project(x => x.Tag);
Where Tag is a string property in your model.
It appears that Aggregate() and all the MongoDB driver operators are closer to Aggregation Framework than C# syntax allows them to be.
Based on your code the result variable is supposed to be of type String which gets translated by the driver into MongoDB.Bson.BsonString however Aggregation Framework always returns BSON documents (single one in this case) so MongoDB .NET driver cannot handle such deserialization in the runtime (BsonDocument -> BsonString).
First workaround is obvious - return anything that resembles BSON document and can be deserialized from BsonDocument type like:
collection.Aggregate().Project(x => new { x.Tag });
and then map results in memory (same query is run behind the scenes)
Another approach: translate your query into LINQ using .AsQueryable() which allows you to return results in more flexible manner:
collection.AsQueryable().Select(x => x.Tag);
In both cases the query that's generated for my projection looks the same:
{aggregate([{ "$project" : { "Tag" : "$Tag", "_id" : 0 } }])}
A bit late but this had a similar issue and this would have solved it for me:
You will want to make an intermediate class to represent group { Tag = v.Key, Count = v.Count() } and then change the Project to this.
.Project(Builders<YourIntermediateClass>.Projection.Expression(x => x.Tag))
I hope this is not a duplicate but I wasn't able to find an answer on this.
It either seems to be an undesired behavior or missing knowledge on my part.
I have a list of platform and configuration objects. Both contains a member string CodeName in it.
The list of CodeNames look like this:
dbContext.Platforms.Select(x => x.CodeName) => {"test", "PC", "Nintendo"}
dbContext.Configurations.Select(x => x.CodeName) => {"debug", "release"}
They are obtained from a MySQL database hence the dbContext object.
Here is a simple code that I was to translate in LINQ because 2 foreach are things of the past:
var choiceList = new List<List<string>>();
foreach (Platform platform in dbContext.Platforms.ToList())
{
foreach (Configuration configuration in dbContext.Configurations.ToList())
{
choiceList.Add(new List<string>() { platform.CodeName, configuration.CodeName });
}
}
This code gives my exactly what I want, keeping the platform name first which looks like :
var results = new List<List<string>>() {
{"test", "debug"},
{"test", "release"},
{"PC", "debug"}
{"PC", "release"}
{"Nintendo", "debug"}
{"Nintendo", "release"}};
But if I translate that to this, my list contains item in a different order:
var choiceList = dbContext.Platforms.SelectMany(p => dbContext.Configurations.Select(t => new List<string>() { p.CodeName, t.CodeName })).ToList();
I will end up with this, where the platform name isn't always first, which is not what is desired:
var results = new List<List<string>>() {
{"debug", "test"},
{"release", "test"},
{"debug", "PC"}
{"PC", "release"}
{"debug", "Nintendo"}
{"Nintendo", "release"}};
My question is, is it possible to obtain the desired result using LINQ?
Let me know if I'm not clear or my question lacks certain details.
Thanks
EDIT: So Ivan found the explanation and I modified my code in consequence.
In fact, only the Enumerable in front of the SelectMany needed the .ToList().
I should also have mentioned that I was stuck with the need of a List>.
Thanks everyone for the fast input, this was really appreciated.
When you use
var choiceList = dbContext.Platforms.SelectMany(p => dbContext.Configurations.Select(t => new List<string>() { p.CodeName, t.CodeName })).ToList();
it's really translated to some SQL query where the order of the returned records in not defined as soon as you don't use ORDER BY.
To get the same results as your nested loops, execute and materialize both queries, and then do SelectMany in memory:
var platforms = dbContext.Platforms.ToList();
var configurations = dbContext.Configurations.ToList();
var choiceList = platforms.SelectMany(p => configurations,
(p, c) => new List<string>() { p.CodeName, c.CodeName })
.ToList();
Rather than projecting it out to an array, project it out two a new object with two fields (potentially an anonymous object) and then, if you need it, project that into a two element array after you have retrieved the objects from the database, if you really do need these values in an array.
Try this-
var platforms= dbContext.Platforms.Select(x=>x.CodeName);
var configurations=dbContext.Configurations.Select(x=>x.CodeName);
var mix=platforms.SelectMany(num => configurations, (n, a) => new { n, a });
If you want to learn more in detail- Difference between Select and SelectMany
I'm new to LinQ and started working with it a couple weeks ago. So maybe i have the wrong understanding how LinQ works. i also tried to use an empty constructor as stated here: Invoke constructor inside a linq query
I changed the constructor of a class. I added the entity energyType
public CSVMeter(int meterID, DateTime datum, int numberOfValues, double total, double total2, double qualityScore, int energyType)
{
this.MeterID = meterID;
this.Datum = datum;
this.NumberOfValues = numberOfValues;
this.total = total;
this.total2 = total2;
this.qualityScore = qualityScore;
this.energyType = energyType;
}
I have the following LinQ query.
public List<CSVMeter> getDisctinctMeters(List<CSVMeter> meterList)
{
newMeterList = newMeterList.GroupBy(x => new { x.MeterID, x.Datum })
.Select(x => new CSVMeter(
x.Key.MeterID,
x.Key.Datum,
x.Sum(s => s.NumberOfValues),
x.Sum(s => s.total),
x.Sum(s => s.total2),
0,
x.energyType))
.ToList();
return meterList;
}
but i get the following error at x.energyType
Error 2 'System.Linq.IGrouping' does not contain a definition for 'energyType' and no extension method 'energyType' accepting a first argument of type 'System.Linq.IGrouping' could be found (are you missing a using directive or an assembly reference?) c:\users\rudi\documents\visual studio 2013\projects\plooscassandra\plooscassandra\handlers\meterbuilder.cs 108 136 PloosCassandra
Why can't i find x.energyType ?
Since you do a grouping, the x in the Select's lambda is an IGrouping<CsvMeter> - not a single CsvMeter.
This IGrouping<CsvMeter> has a Key property of the anonymous type you created above and further represents a sequence of CsvMeter instances.
Since you already did sum Sum operations on that sequence, you actually should have known that. The question is, how you want to aggregate the energyType of all CsvMeter instances in the sequence to evaluate the energyType for your resulting new CsvMeter. Here is how to simply use the energyType of the first element in the group:
newMeterList.GroupBy(x => new { x.MeterID, x.Datum })
.Select(x => new CSVMeter(
x.Key.MeterID,
x.Key.Datum,
x.Sum(s => s.NumberOfValues),
x.Sum(s => s.total),
x.Sum(s => s.total2),
0,
x.First().energyType))
.ToList();
First() returns the first CsvMeter instace in the group.
But maybe you actually wanted to group the CsvMeter instances by that energy type, too. So you could change the grouping to
newMeterList.GroupBy(x => new { x.MeterID, x.Datum, x.energyType })
//...
and use that value in the Select:
.Select(x => new CSVMeter(
x.Key.MeterID,
x.Key.Datum,
x.Sum(s => s.NumberOfValues),
x.Sum(s => s.total),
x.Sum(s => s.total2),
0,
x.Key.energyType))
BTW your code look a little weird: your getDisctinctMeters takes a parameter called meterList and that is returned without any change. Instead you use some newMeterList and did not show its declaration. For my answer I assumed your intention was this:
public List<CSVMeter> getDisctinctMeters(List<CSVMeter> meterList)
{
var newMeterList = meterList.GroupBy // shortened for clarity
return newMeterList;
}
The return type of GroupBy is IGouping, representing the groups themselves, not IEnumerable<CSVMeter>, so x has no property energyType. That being said, you would have to clarify for which member of the group the energyType would have to be selected; energyType is not used in the grouping expression.
I have an entity framework object called batch, this object has a 1 to many relationship to items.
so 1 batch has many items. and each item has many issues.
I want to filter the for batch items that have a certain issue code (x.code == issueNo).
I have written the following but Im getting this error:
items = batch.Select(b => b.Items
.Where(i => i.ItemOrganisations
.Select(o => o
.Issues.Select(x => x.Code == issueNo))));
Error 1:
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<System.Collections.Generic.IEnumerable<bool>>' to 'bool'
Error 2:
Cannot convert lambda expression to delegate type 'System.Func<Ebiquity.Reputation.Neptune.Model.Item,bool>' because some of the return types in the block are not implicitly convertible to the delegate return type
Select extension method needs a lambda expression that returns a boolean, but the inner o.Issues.Select returns an IEnumerable of boolean to the outer Select(o => o which result in the exception you're getting.
Try using Any instead which verifies that at least one element verifies the condition:
items = batch.Select(
b => b.Items.Where(
i => i.ItemOrganisations.Any(
o => o.Issues.Any(x => x.Code == issueNo)
)
)
);
If I understand correctly, you're trying to select through multiple layers of enumerables. In those cases you need SelectMany which flattens out the layers, not Select. LINQ's syntax sugar is made specifically to make SelectMany easier to reason about:
var items = from item in batch.Items
from org in item.ItemOrganizations
from issue in org.Issues
where issue.Code == issueNo
select item;
The compiler translates that into something like this:
var items = batch.Items
.SelectMany(item => item.ItemOrganizations, (item, org) => new {item, org})
.SelectMany(#t => #t.org.Issues, (#t, issue) => new {#t, issue})
.Where(#t => #t.issue.Code == issueNo)
.Select(#t => #t.#t.item);
You can always wrap this in a Distinct if you need to avoid duplicate items:
var items = (from item in batch.Items
from org in item.ItemOrganizations
from issue in org.Issues
where issue.Code == issueNo
select item).Distinct();
It's hard to tell what you're trying to do based on your code but I think you're looking for something like this;
var issue = batch.Select(b => b.Items).Select(i => i.Issues).Where(x => x.Code == issueNo).Select(x => x).FirstOrDefault();
The above query will return the first issue where the Issues Code property is equal to issueNo. If no such issue exists it will return null.
One problem (the cause of your first error) in your query is that you're using select like it's a where clause at the end of your query. Select is used to project an argument, when you do Select(x => x.Code == issueNo) what you're doing is projecting x.Code to a bool, the value returned by that select is the result of x.Code == issueNo, it seems like you want that condition in a where clause and then you want to return the issue which satisfies it which is what my query is doing.
items = from b in batch.Include("Items")
where b.Items.Any(x=>x.Code==issueNo)
select b;
You're getting lost in lambdas. Your LINQ chains are all embedded in each other, making it harder to reason about. I'd recommend some helper functions here:
static bool HasIssueWithCode(this ItemOrganization org, int issueNo)
{
return org.Issues.Any(issue => issue.Code == issueNo);
}
static bool HasIssueWithCode(this Item items, int issueNo)
{
return items.ItemOrganizations.Any(org => org.HasIssueWithCode(issueNo));
}
Then your answer is simply and obviously
var items = batch.Items.Where(item => item.HasIssueWithCode(issueNo));
If you inline these functions, the result is the exact same as manji's (so give manji credit for the correct answer), but I think it's a bit easier to read.
I'm writing an ASP.NET Web Pages application and in it, I have a massive LINQ to Entities query. This query pulls data from a table in the database, filters it, groups the data twice, and adds extra properties to the result set. I then loop through the table, outputting the rows.
The query is quite big, sorry:
accountOrders = db.EventOrders
.Where(order => order.EventID == eventID)
.OrderBy(order => order.ProductCode)
.GroupBy(order => new { order.AccountNum, order.Exhibitor, order.Booth })
.Select(orders =>
new {
Key = orders.Key,
ProductOrders = orders
.GroupBy(order => new { order.ProductCode, order.Product, order.Price })
.Select(productOrders =>
new {
Key = productOrders.Key,
Quantity = productOrders.Sum(item => item.Quantity),
HtmlID = String.Join(",", productOrders.Select(o => (o.OrderNum + "-" + o.OrderLine))),
AssignedLines = productOrders.SelectMany(order => order.LineAssignments)
})
})
.Select(account =>
new {
Key = account.Key,
// Property to see whether a booth number should be displayed
HasBooth = !String.IsNullOrWhiteSpace(account.Key.Booth),
HasAssignedDigitalLines = account.ProductOrders.Any(order => order.AssignedLines.Any(line => line.Type == "digital")),
// Dividing the orders into their respective product group
PhoneOrders = account.ProductOrders.Where(prod => ProductCodes.PHONE_CODES.Contains(prod.Key.ProductCode)),
InternetOrders = account.ProductOrders.Where(prod => ProductCodes.INTERNET_CODES.Contains(prod.Key.ProductCode)),
AdditionalOrders = account.ProductOrders.Where(prod => ProductCodes.ADDITIONAL_CODES.Contains(prod.Key.ProductCode))
})
.ToList();
I use the added properties to help style the output. For example, I use HasBooth property to check whether or not I should output the booth location in brackets beside the exhibitor name. The problem is I have to save this big query as an IEnumerable, meaning I get the error: Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type. Should I even be manipulating the query this way?
Any advice is much appreciated!
At some point, you are passing in a dynamic datatype to the method, which in turn changes the return type to simply dynamic. You can either cast the dynamic type to a type that is recognised at compile time or explicitly set the return type instead of using var.
You can read more about this issue here: http://www.mikesdotnetting.com/Article/198/Cannot-use-a-lambda-expression-as-an-argument-to-a-dynamically-dispatched-operation