c# groupBy failing with key not found exception - c#

I retrieve BsonDocuments using the mongoDb driver in C#.
After this, I have a grouping method used to regroup my documents with a given Key.
Here is an example of objects returned from mongo :
{{ "_id" : ObjectId("57762d37de7d9c1cbc53bc10"), "DraftNumber" : "227232AA", "EndDate" : ISODate("2016-09-29T08:45:14.986Z"), "ProjectNumber" : "17E618BB" }}
Here is the beginning of my method used to regroup :
internal List<Project> RegroupProject(IEnumerable<BsonDocument> projects)
{
var regroupProjectList = new List<Project>();
var groupedProject = projects.GroupBy(project => project["ProjectNumber"]).ToList();
[...]
}
And I got the following exception en groupBy expression :
Element 'ProjectNumber' not found.
What do you think about that ?
Is it because one of my 18k element has no value for ProjectNumber field ?

You have guessed correctly that one or more of the 18k items doesn't have a ProjectNumber property. There's a couple of options that I can see.
Filter out the items that don't have the property:
var groupedProject = projects
.Where(p => p.Contains("ProjectNumber"))
.GroupBy(project => project["ProjectNumber"])
.ToList();
Specify a default value for items missing the value:
var groupedProject = projects
.GroupBy(project => project["ProjectNumber", "<DefaultValue>"])
.ToList();

Related

newbie to CosmoDB how to query collection with multiple values?

I have the following collection and I want to query based on Class and FullName from Students
{
"id" : "ABCD",
"Class" : "Math",
"Students" : [
{
"FullName" : "Dan Smith",
},
{
"FullName" : "Dave Jackson",
},
]
}
The following filter works based on class.
var filter = builder.Eq(x => x.Class, "Math");
var document = collection.Find(filter).FirstOrDefaultAsync();
But I want to query based on student also, I tried to add another filter and it has "Cannot implicitly convert type string to bool" error
filter &= builder.Eq(x => x.Students.Any(y => y.FullName,"Dan"));
As you want to query with the nested document in an array, you need $elemMatch operator. In MongoDB .NET Driver syntax, you can achieve with either of these ways:
Solution 1: ElemMatch with LINQ Expression
filter &= builder.ElemMatch(x => x.Students, y => y.FullName == "Dan");
Solution 2: ElemMatch with FilterDefinition
filter &= builder.ElemMatch(x => x.Students,
Builders<Student>.Filter.Eq(y => y.FullName, "Dan"));
The above methods will return no document as the filter criteria don't match with the attached document.
If you look for matching the partial word, you need to work with $regex operator.
Solution: With regex match
filter &= builder.ElemMatch(x => x.Students,
Builders<Student>.Filter.Regex(y => y.FullName, "Dan"));
Demo

Problem returning array of strings instead of BsonDocument Mongodb c# driver Unwind

I have got a class called Contact which has a field named Numbers and Numbers is a list of strings. I want to return a list containing all numbers in matched documents only. but it gives me an array of BsonDocuments. I need Just a List of numbers.
here is my query:
var query = await _context.ContactLists.Aggregate(new AggregateOptions { AllowDiskUse = true })
.Match(x => x.Id == id && x.CreatorId == user.GetUserId())
.Unwind(x => x.Numbers)
.Project(Builders<BsonDocument>.Projection.Include("Numbers").Exclude("_id"))
.ToListAsync();
it resturns :
[{{ "Numbers" : "989309910790" }}]
and I need :
["989309910790"]
I am not allowed to use Linq driver.

LINQ to SQL filter child collection

I'm strugling with this query, i think I'm missing something.
I have two autogenerated dbml models.
public partial class RegulatorsOrganizationView
{
private int regulatorOrgId;
private string regulatorOrgName;
private EntitySet<RegulatorsView> regulatorsViews;
}
public partial class RegulatorsView
{
private int regulatorId;
private string regulatorName;
}
I need to apply filtering by name, input string "filterText" should be a part of regulatorName
If regulator is not matching - should be filtered out from regulatorsViews
If regulatorOrganizationView have at least one match in regulatorsViews - should be included
If regulatorsViews collection of regulatorOrganizationView does not have regulators that match condition, but it's name contains filterText - it should be included.
Currently I'm loading all the matching regualatorsOrganizationViews, and do filtering on regulators down the line.
List<RegulatorOrganizationView> regOrgs = boatDataContext.RegulatorOrganizationView
.Where(r => r.RegulatorsViews.Any(ar => ar.regulatorName.ToUpper().Contains(filterText.ToUpper()))
|| r.regulatorName.ToUpper().Contains(filterText.ToUpper())
.ToList();
But this way I'm loading redundent Regulators only to filter them out later on.
How can I rebuild this query to load only matching regulators from starters ?
It tried to use Select() to assign regulatorOrgnization filter list of Regulators.
regulatorsOrgs = DataContext.RegulatorOrganizationViews
.Where(ro => ro.regulatorOrgName.ToUpper().Contains(filterText.ToUpper())
|| ro.RegulatorsViews.Any(r => r.regulatorName.ToUpper().Contains(filterText.ToUpper()))
.Select(ro => new RegulatorOrganizationView()
{
regulatorId = ro.regulatorId,
regulatorOrgName = ro.regulatorOrgName,
RegulatorsViews = ro.RegulatorsViews
.Where(r => r.regulatorName.ToUpper().Contains(filterText.ToUpper())
.Select(r => new RegulatorsView()
{
regulatorId = r.regulatorId,
regulatorName = r.regulatorName,
}).ToEntitySet()
}).ToList();
But I'm getting exception: Message="The explicit construction of the entity type 'RegulatorsOrganizationView' in a query is not allowed."
Looks like filtered Include() would be an option (like in EF) but I can't find a way to use it with Linq To SQL.
Any ideas ?
In LINQ-to-SQL it's a bit messy and not intuitive to do this. You have to use DataLoadOptions:
var opt = new DataLoadOptions();
opt.AssociateWith((RegulatorsOrganizationView v)
=> v.regulatorsViews.Where(ar => ar.regulatorName.Contains(filterText)));
opt.LoadWith((RegulatorsOrganizationView v) => => v.regulatorsViews);
DataContext.LoadOptions = opt;
var result = DataContext.RegulatorOrganizationViews
.Where(ro => ro.regulatorOrgName.Contains(filterText)
&& ro.regulatorsViews.Any());
So this says: when loading RegulatorOrganizationViews, then when their regulatorsViews are associated, make them meet the given condition.
Then it says: when when loading RegulatorOrganizationViews, also load their regulatorsViews.
The latter is like Include in Entity Framework. The former makes it behave like filtered Include, or maybe closer, a global query filter.
I removed the ToUpper calls for brevity, but you don't need them if the database collation is case-insensitive.

Receiving an unclear error when using SelectMany() on an IGrouping

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()

Unable to cast object of type 'MongoDB.Bson.BsonString' to type 'MongoDB.Bson.BsonDocument' in MongoDB .NET Driver

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))

Categories