Add item in nested array (mongodb and C#) - c#

I have the following document called Attendances
{
"_id" : ObjectId("5a4ffb00762caf6b54f61ebb"),
"AttnDate" : ISODate("2018-01-05T22:24:00.490Z"),
"AllAttendances" : [
{
"FullName" : "DOMAIN\Zack",
"Logged" : ISODate("2018-01-05T22:23:46.835Z"),
"Pauses" : [
{
PauseStartAt: ISODate("2018-01-05T22:30:46.835Z"),
PauseEndAt: ISODate("2018-01-05T22:35:46.835Z")
}
]
}
]
}
How can i add new items to Pauses. This is my attempt but i have this error "Cannot convert lambda expression to type 'fielddefinition because it is not a delegate type.
My attempt
var filter = Builders<Attendance>.Filter.Eq(a => a.Id, currentAttn.Id) & Builders<Attendance>.Filter.ElemMatch(s => s.AllAttendances, Builders<TimeRecord>.Filter.Eq(n => n.FullName, userName));
var update = Builders<Attendance>.Update.Push(e => e.AllAttendances[-1].Pauses, pauses);
context.Attendances.FindOneAndUpdate(filter, update);
I followed this guide
Attendance Class
public class Attendance
{
[JsonConverter(typeof(ObjectIdConverter))]
public ObjectId Id { get; set; }
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime AttnDate { get; set; }
public List<TimeRecord> AllAttendances { get; set; }
}
TimeRecord Class (AllAttendances)
public class TimeRecord
{
public string FullName { get; set; }
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime Logged { get; set; }
public List<Pause> Pauses { get; set; }
}
Pause Class
public class Pause
{
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime PauseStartedAt { get; set; }
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime PauseEndedAt { get; set; }
}

You need to update your filter to
var filter = Builders<Attendance>.Filter.Eq(a => a.Id, id) &
Builders<Attendance>.Filter.ElemMatch(s => s.AllAttendances, x => x.FullName == userName);
The first argument of ElemMatch is the field, the second argument is the filter.

Looking at it from a different angle, I would suggest you don't use ObjectIDs in c#. I always define ObjectIds as strings in my models and use the Bson attribute decorators to define them as ObjectId's in the database
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
Purely for the pain it causes trying to use ObjectIds in C#. Strings are much easier to handle. Your document in mongodb will still look the same, and you will not need to cast as object id's in your code at all:
_id : ObjectId("xxxxxxx")
This should help you get around the issue of the compiler not knowing how to do the conversion

Related

MongoDB C# filter array object with array filter

I have a array filed on model:
public IList<RelationModel> RawMaterials { get; set; }
public class RelationModel
{
public int EntityID { get; set; }
public string Title { get; set; }
}
and a array for FilterDefinition:
public IList<int> Materials { get; set; }
Now I want make filter for this tow array
var filter = Builders<model>
.Filter
.ElemMatch(m => m.RawMaterials, {EntityID cointains Materials});
Please help me!
Assume that the root document model schema as below:
public class Model
{
public IList<RelationModel> RawMaterials { get; set; }
}
$elemMatch is part of the right way to access the nested element. While you also need the $in operator to filter the ID of the RelationModel object within the Materials array.
In MongoDB .NET Driver syntax, it should be:
var filter = Builders<Model>.Filter.ElemMatch(m => m.RawMaterials,
Builders<RelationModel>.Filter.In(x => x.EntityID, Materials));

How to search in nested object for multiple values on same field in Elasticsearch with NEST Library?

I have nested documents such as;
public sealed class CampaignIndexModel : ElasticEntity<Guid>
{
public Guid StoreId { get; set; }
public string Slug { get; set; }
public string SlugKey { get; set; }
public string Title { get; set; }
public string Code { get; set; }
public string Description { get; set; }
public string Condition { get; set; }
public string PreviewImageUrl { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public bool IsPublished { get; set; }
public DateTime CreatedOnUtc { get; set; }
[Nested]
public List<BadgeIndexModel> Badges { get; set; }
}
public class BadgeIndexModel
{
public string Code { get; set; }
public string Name { get; set; }
}
I'd like to query in nested object with multiple values. For example, I need to query which included Code property which are "AD", "NEW". All documents must have badge and their code properties must be "AD" and "NEW". The code properties can be dynamically. Actually I'd like to search list of string in the nested object's code property.
Note that the classes are auto-mapped while creating indexes.
I hope the question is clear, understandable.
Thank you.
UPDATE
As far as I researched Elasticsearch documentations, as below, the query result returns match exactly given badges codes.
q.Bool(b=>b
.Must(x=>x.
Nested(n=>n
.Path(p=>p.Badges)
.Query(qq=>qq
.Term(t=>t
.Field(f=>f.Badges.First().Code.Suffix("keyword"))
.Value(badge))))))
Then, the answer which is marked correct, returns documents which contains badge codes
I know it has been a little while you posted the question. But here you go -- You could do this by creating a Nested Query within which you could filter upon your list and pass this to your search method. Below method shows how this can be done. This takes the list of strings that you want to use as values for codes.
private static QueryContainer BuildNestedQuery(List<string> badgeCodes)
{
// badgeCodes is your list of strings that you want to filter on
return new QueryContainerDescriptor<CampaignIndexModel>()
.Nested(n =>
n.Path(c => c.Badges)
.Query(q => q
.Terms(t => t
.Field(f => f.Badges.FirstOrDefault().Code)
.Terms(badgeCodes.ToArray())
)
)
)
}
This QueryContainer can further be passed to the Search method of the NEST client like something shown below. However, please bear in mind that there could be slight changes in the way you trigger the client's search method depending on how you're doing it, but hooking it to the search method remains more or less the same as shown below.
// replace T with type of your choice
// client is a reference to NEST client
var result = client.Search<T>(
.From(0)
.Size(20)
.Query(q => BuildNestedQuery(badgeCodesList))
// other methods that you want to chain go here
)

Adding more entries replaces everything instead of adding

Good day I am new in mongodb, I can successfully perform a CRUD, but now I want to dig deep into mongoDB. I have this JSON and I want it to update my database once it has a data, and create a new one if it doesn't exist, but what happens is it always replace my value for the "LeaderboardDetails".
Here is the MongoDB JSON:
{
"id" : "secretsomething",
"UserID" : "zgahsjd",
"category" : "testing",
"Score" : 2000,
"Badges" : 0,
"LeaderboardDetails": {
"id": "123123123213",
"ScoreForDay": 10000,
"BadgesForDay": 0
}
}
When I submit to update the "LeaderboardDetails" it should add a new entry if it doesn't exist, otherwise update the current one , but instead it replaces it.
Here is my code for PUT:
public void Update(string id, SomeModel newScore)
{
_record.ReplaceOne(scores => scores.id == id, newScore);
}
Here is the SomeModel Code:
[Required]
[JsonProperty(PropertyName = "UserID")]
public string UserID { get; set; }
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string id { get; set; }
[Required]
[JsonProperty(PropertyName = "category")]
public string category { get; set; }
[Required]
public int Score { get; set; }
[Required]
public int Badges { get; set; }
public class LeaderboardIDToSend
{
public string id;
public string ScoreForDay;
public string BadgesForDay;
public LeaderboardIDToSend(string _id, string _score, string _badges)
{
id = _id;
ScoreForDay = _score;
BadgesForDay = _badges;
}
}
[Required]
public LeaderboardIDToSend LeaderboardDetails;
Looking at your json, leaderBoardDetails is an object, but it should be a list of objects, that is the first modification you should do, second, in order for you to add an item to set, you should use Builders<T>.Update.AddToSet(c => c.leaderBoardDetails, leaderboardDto), now regarding your concern, you want to upsert the object, as far as I know, and looking at Array Update Operators, there is no such operator, so you will have to do this manually, you need to load all your existing leader, then check if this id exist, if so, you need to update its values, and then Update the whole list (leaderBoardDetails), if it doesn't exist, you can simple use AddToSet operator

Get subcollection of MongoDB collection with C# driver based on search

I have this project with https://github.com/Mech0z/Foosball/blob/master/Models/Old/PlayerRankHistory.cs
I have the following classes where PlayerRankHistory is saved in MongoDB, this contains a list of PlayerRankHistorySeasonEntry which each contains PlayerRankHistoryPlot.
I would then like to provide an email of a player and a seasonname and then only get the list PlayerRankHistoryPlots out as a list, but the code I have written is very slow and not faster than just providing only an email and getting much more data out
And as a side note, not sure how to write it to make it async
The query I have now is
public async Task<List<PlayerRankHistoryPlot>> GetPlayerRankEntries(string email, string seasonName)
{
var query = Collection.AsQueryable().SingleOrDefault(x => x.Email == email)
.PlayerRankHistorySeasonEntries.SingleOrDefault(x => x.SeasonName == seasonName).HistoryPlots;
List<PlayerRankHistoryPlot> result = query.ToList();
return result;
}
public class PlayerRankHistory
{
public PlayerRankHistory(string email)
{
Email = email;
PlayerRankHistorySeasonEntries = new List<PlayerRankHistorySeasonEntry>();
}
public Guid Id { get; set; }
public string Email { get; set; }
public List<PlayerRankHistorySeasonEntry> PlayerRankHistorySeasonEntries { get; set; }
}
public class PlayerRankHistorySeasonEntry
{
public PlayerRankHistorySeasonEntry(string seasonName)
{
SeasonName = seasonName;
HistoryPlots = new List<PlayerRankHistoryPlot>();
}
public string SeasonName { get; set; }
public List<PlayerRankHistoryPlot> HistoryPlots { get; set; }
}
public class PlayerRankHistoryPlot
{
public PlayerRankHistoryPlot(DateTime date, int rank, int eloRating)
{
Date = date;
Rank = rank;
EloRating = eloRating;
}
public DateTime Date { get; set; }
public int Rank { get; set; }
public int EloRating { get; set; }
}
An example of a document
{"_id":"AYU3e3Qgw0Gut1fngze80g==","Email":"someemail#gmail.com","PlayerRankHistorySeasonEntries":[{"SeasonName":"Season 1","HistoryPlots":[{"Date":"2020-01-10T12:24:12.511Z","Rank":11,"EloRating":1488},{"Date":"2020-01-13T12:51:41.597Z","Rank":12,"EloRating":1488},{"Date":"2020-01-15T11:11:43.223Z","Rank":10,"EloRating":1510},{"Date":"2020-01-15T11:11:45.049Z","Rank":8,"EloRating":1530},{"Date":"2020-01-15T12:14:58.042Z","Rank":9,"EloRating":1530},{"Date":"2020-01-15T12:14:59.886Z","Rank":8,"EloRating":1530}]}]}
I believe when you define Collection.AsQueryable().FirstOrDefault(), you are pulling all records in that collection and then filtering through them. You should use the Find() method that is provided by MongoDB C# driver to filter the records which is much faster as well.
Get the PlayerRankHistory objects based on the email address
From on the filtered records, only return the records that have the required season
Get the HostoryPlots for only the first match as list
Collection.Find(Builders<PlayerRankHistory>.Filter.Eq(x => x.Email, email))
.Select(y => y.PlayerRankHistorySeasonEntries.Where(z => z.SeasonName.Equals(seasonName)))
.FirstOrDefault()?.HistoryPlots
.ToList();

Filtering out certain child objects during JSON conversion

I'm not sure how to explain our goal in words, so I'll hop straight to the code.
With our current json converter settings. We get the following result when converting one of our events.
{
"PortfolioId": {
"Id": "portId"
},
"EntityId": {
"Id": "3cf7582b-3cad-4aeb-a671-0132ba97d60d"
},
"EventVersion": 1,
"OccurredOn": "2018-08-08T09:52:03.7871323+02:00",
"Id": "71fe3a2e-354a-4b19-abea-655471e96d72",
"Creator": {
"Id": "27a1d6b1-1ffa-4071-92ee-31c12bf120f0"
},
"CorrelationId": "3138dbe0-3a4d-4559-83e9-d1f3e5684ee8"
}
Our goal is to get a converted event that looks like this;
{
"PortfolioId": "portId",
"EntityId": "3cf7582b-3cad-4aeb-a671-0132ba97d60d",
"EventVersion": 1,
"OccurredOn": "2018-08-08T09:52:03.7871323+02:00",
"Id": "71fe3a2e-354a-4b19-abea-655471e96d72",
"Creator": "27a1d6b1-1ffa-4071-92ee-31c12bf120f0",
"CorrelationId": "3138dbe0-3a4d-4559-83e9-d1f3e5684ee8"
}
In the event we have an object of a certain type (i.e EntityId, PortfolioId) which holds the value in a property. All these Id types derive from an abstract class with the property "Id".
An event class looks like this.
public class ExampleEvent : DomainEvent
{
public PortfolioId PortfolioId { get; }
public EntityId EntityId { get;}
public ExampleEvent(
PortfolioId portfolioId,
EntityId entityId,
UserId creator, Guid correlationId) : base(creator, correlationId)
{
PortfolioId = portfolioId;
EntityId = entityId;
}
}
Does anybody have an idea how one could do this. I thought this might be possible with a custom json converter but have no idea yet on how to implement this for this case.
EDIT: I should have stated that this has to be done one many event types. And that a generic reusable solution seems to fit best in order to keep the overhead low. This mean that it is probably best if the event class itself is not altered at all. (so preferably without attributes etc)
The second Approach in this answer could help in manipulating the serialization.
Making a property deserialize but not serialize with json.net
You can use JsonIgnore attributes with calculated properties:
public class PortfolioId
{
public string Id { get; set; }
}
public class EntityId
{
public string Id { get; set; }
}
public class UserId
{
public string Id { get; set; }
}
public class ExampleEvent
{
private ExampleEvent() // for JSON deserializer
{
Creator = new UserId();
Portfolio = new PortfolioId();
Entity = new EntityId();
}
// add your base constructor call
public ExampleEvent(PortfolioId portfolio, EntityId entity, UserId creator)
{
Creator = creator;
Portfolio = portfolio;
Entity = entity;
}
[JsonIgnore]
public UserId Creator { get; set; }
public string CreatorId
{
get => Creator.Id;
set => Creator.Id = value;
}
[JsonIgnore]
public PortfolioId Portfolio { get; set; }
public string PortfolioId
{
get => Portfolio.Id;
set => Portfolio.Id = value;
}
[JsonIgnore]
public EntityId Entity { get; set; }
public string EntityId
{
get => Entity.Id;
set => Entity.Id = value;
}
public int EventVersion { get; set; }
public string Id { get; set; }
public string CorrelationId { get; set; }
}
Incase JsonIgnore does not suites your need or you need more customization you may also look for IContractResolver with JsonProperty.ShouldDeserialize / JsonProperty.ShouldSerialize. here some examples.

Categories

Resources