MongoDB array object Update and Page - mongodb

I've a entity class stored in MongoDB that looks similar to these:
public class Theme
{
public ObjectId Id { get; set; }
public string Description { get; set; }
public string Title { get; set; }
public List<Comment> CommentList { get; set; }
}
public class Comment
{
public string Content { get; set; }
public string Creator { get; set; }
public string Date { get; set; }
}
Using the MongoDB C# driver,and based on the model, how can i resolve for the following questions:
Update a comment of the theme, eg: theme1.CommetList[10].Creator = "Jack"
How to page for the array object
Thanks.
Wentel
#Andrew Orsich
Thanks for your help.
And there is another trouble:
var query = Query.EQ("Id", id); //The 'id' type is ObjectId
List<Theme> newDatas = themeCollection.FindAs<Theme>(query).ToList();
Theme newData = themeCollection.FindOneByIdAs<Theme>(allDatas[0].Id);
Result: 'newDatas' is null and 'newData' has data, why?

1.Using positional operator:
var query = Query.And(Query.EQ("Id", id));
var update = Update.Set("CommetList.10.Creator", "Jack");
Also you probably need to add id to the Comment class. In this case you can updated matched by query comment like this:
var query = Query.And(Query.EQ("Id", id), Query.EQ("CommentList.Id", commentId));
var update = Update.Set("CommentList.$.Creator", "Jack");
2.You can load entire theme and do paging of comments from c# using linq for example. Or you can also use $slice like this:
var comments = themeCollection
.FindAs<Comment>()
.SetFields(Fields.Slice("Comments", 40, 20))
.ToList();

For your second question you need to do the following:
ObjectId oid = new ObjectId(id);
var query = Query.EQ("_id", oid);

Related

Linq query to join three tables and return a object along with a list of another object in Entity Framework Core

I have three tables, Organization, Department, and OrganizationDepartments. here is the relationship between them.
Now I would like to join these three tables and create another object for a DTO class. This DTO object has some properties and a list of other DTOs. Here is the DTO Class.
Organization DTO:
public class OrganizationDto
{
public string Id { get; set; }
public string OrganizationName { get; set; }
public string Logo { get; set; }
public bool? IsActive { get; set; }
public IList<OrganizationDepartmentDto> OrganizationDepartments { get; set; }
}
OrganizationDepartment DTO:
public class OrganizationDepartmentDto
{
public string OrganizationId { get; set; }
public string OrganizationName { get; set; }
public string DepartmentId { get; set; }
public string DepartmentName { get; set; }
}
Now I would like to write a LINQ query to get a Organization object along with all the departments related to that organization. The query is imcomplete because I don't know how can I get all the department information as list in a single query. The code is below:
var organizationInfo = (from org in _dbContext.Organizations
join orgDept in _dbContext.OrganizationDepartments on org.Id equals orgDept.OrganizationId
join dept in _dbContext.Departments on orgDept.DepartmentId equals dept.Id
where org.Id.ToUpper() == id.ToUpper()
orderby org.CreatedOn ascending
select new OrganizationDto
{
Id = org.Id,
OrganizationName = org.OrganizationName,
Logo = org.Logo,
IsActive = org.IsActive,
OrganizationDepartments = //TODO:..
}
);
Can anyone help me to get the department lists of that organization's object (see the TODO:)?
If your entities are mapped correctly, and the relationships are correctly configured.
you can use .Include("OrganizationDepartment") and .ThenInclude("Department")to ensure relations are included into the generated Query.
If you insist on using Query Syntax. e.g from org in context.Organization
you can write out the query like this.
var q = (from org in _dbContext.Organizations
where org.Id.ToUpper() == id.ToUpper()
orderby org.CreatedOn ascending
select new OrganizationDto
{
Id = org.Id,
OrganizationName = org.OrganizationName,
Logo = org.Logo,
IsActive = org.IsActive,
OrganizationDepartments = org.OrganizationDepartments.ToList()
}
Depending on your usecase. Sometimes you are not interested in actually showing the "many to many" table outside of the scope of your database.
so it might make more sense to actually flatten the Dto.
that query would look like
var q = (from org in _dbContext.Organizations
where org.Id.ToUpper() == id.ToUpper()
orderby org.CreatedOn ascending
select new OrganizationDto
{
Id = org.Id,
OrganizationName = org.OrganizationName,
Logo = org.Logo,
IsActive = org.IsActive,
Departments= org.OrganizationDepartments.Select(t => t.Departments).ToList()
}

.NET Core MongoDB. Find by guid returns null

I have the following setup:
The document:
[BsonCollection("Users")] // I get the collection name with a custom extension
[BsonIgnoreExtraElements]
public class UserDocument
{
[BsonId]
public Guid Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public UserSettingsModel UserSettings { get; set; }
}
public class UserSettingsModel
{
// ...
}
The repository:
public class UserRepository
{
private readonly IMongoCollection<UserDocument> _collection;
private readonly ILogger<UserRepository> _logger;
public UserRepository(IMongoDatabase database, ILogger<UserRepository> logger)
{
// returns "Users"
var collectionName = typeof(UserDocument).GetCollectionName();
_collection = database.GetCollection<UserDocument>(collectionName);
}
// ...
public async Task<UserDocument> GetById(Guid id)
{
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, id);
var user = await _collection.FindAsync(filter);
// var user = await _collection.FindAsync(x => x.Id == id); - doesn't work either
var request = filter.Render(
_collection.DocumentSerializer,
_collection.Settings.SerializerRegistry).ToString();
_logger.LogDebug(request);
return user.FirstOrDefault();
}
}
And I initialize the client this way:
// ...
BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
var client = new MongoClient(connectionString);
var database = client.GetDatabase(dbName);
services.AddSingleton(c => database);
// convention pack and registries
// ...
// if moved here doesn't work either
// BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
The filter generated in GetById is still the following: { "_id" : CSUUID("459f165a-4a91-4f39-906c-dc7401ee2468") } when I expect it to be UUID instead of CSUUID.
So, the query doesn't find anything and returns null. In the database the document I'm searching for has _id: UUID('459f165a-4a91-4f39-906c-dc7401ee2468')
What am I doing wrong?
I was able to fixing by this trick:
var mongoConnectionUrl = new MongoUrl(connectionString);
var mongoClientSettings = MongoClientSettings.FromUrl(mongoConnectionUrl);
// before initializing client
mongoClientSettings.GuidRepresentation = GuidRepresentation.Standard;
However setting the GuidRepresentation in client settings is obsolete, which is quite confusing. Also, the query generated still has CSUUID instead of UUID. I was able to log the query the following way:
mongoClientSettings.ClusterConfigurator = cb =>
{
cb.Subscribe<CommandStartedEvent>(e => logger.LogDebug($"{e.CommandName} - {e.Command.ToJson()}"));
};
If anyone finds a better way and post it here it would be appreciated.

Adding a model with join to another model for many to many without navigation property [duplicate]

This question already has an answer here:
Entity Framework - Inserting model with many to many mapping
(1 answer)
Closed 4 years ago.
How can I insert a model Tag that belongs to a model Post when I have the models setup like this:
Post
public class Post
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public Post()
{
Tags = new List<Tag>();
}
}
Tag
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
}
This question suggests to create a Post object then add Tags to the Tags collection, I couldn't get it working:
Insert/Update Many to Many Entity Framework . How do I do it?
I want to add Tag to Post already in the database, how can I do that with EF. I'm new to EF.
This is what I've tried, if I send this to the API it doesn't insert any records and I can see that the new tag Id = 0 which doesn't exist in the database, but I'd think that'd cause a foreign key constraint error, not sure If I need to do something to auto generate Id for the tag:
{
Name: "test"
}
API
[ResponseType(typeof(Tag))]
public IHttpActionResult PostTag(Tag tag)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var post = new Post();
var tags = new List<Tag>();
tags.Add(tag);
post.Tags.Add(tag);
post.Id = 10;
db.Entry(post).State = EntityState.Modified;
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = tag.Id }, tag);
}
I fetch the Post first add then save changes.
[ResponseType(typeof(Tag))]
public IHttpActionResult PostTag(TagDTO tagDTO)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var post = db.Posts.Find(TagDTO.PostId);
post.Tags.Add(new Tag() { Name = tagDTO.Name });
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = post.Tags.First().Id }, post);
}

How to improve this code of query in mongodb

This is what my employee use
var client = new MongoClient(System.Configuration.ConfigurationManager.AppSettings["MongoDbServer"]);
var server = client.GetServer();
MongoDatabase db = server.GetDatabase("Edbert");
var collection = db.GetCollection("testInstagram");
var query = Query.And(Query.Not(Query.Or(Query.Size("PossibleInstagramIDs", 1), Query.Size("PossibleInstagramIDs", 0))),Query.EQ("InstagramID",BsonNull.Value));
I think this part is ugly:
var query = Query.And(Query.Not(Query.Or(Query.Size("PossibleInstagramIDs", 1), Query.Size("PossibleInstagramIDs", 0))),Query.EQ("InstagramID",BsonNull.Value));
What he tries to do is to set the query to return true if size of PossibleInstagramIDs are bigger than 1.
What should he have done?
I would make the collection strongly typed by creating a TestInstagram class and then use LINQ.
Your TestInstagram class will look something like this:
public class TestInstagram
{
public ObjectId Id { get; set; }
public IEnumerable<int> PossibleInstagramIDs { get; set; }
public int InstagramID { get; set; }
}
And this will allow you to simply query as follows:
var collection = _mongoDatabase.GetCollection<TestInstagram>("testInstagram");
collection.AsQueryable().Where(ti => ti.PossibleInstagramIDs.Count() > 1 && ti.InstagramID == null);

What is right way to deal with "N+1" + Count problem?

Supose the model as below:
class public Post
{
public int Id {get; set;}
public virtual ICollection<Comment> Comments {get;set;}
}
in the Posts/Index Page, I want to show a list of Post, with the Count of comments of each post (not total number of comments of all posts).
1: If I use
context.Posts.Include("Comments")
it will load the whole entity of all related commments , in fact I only need the Count of Comments.
2: If I get the count of each post one by one:
var commentCount = context.Entry(post)
.Collection(p => p.Comments)
.Query()
.Count();
that is a N+1 problem.
Any one knows the right way?
Thank you!
Do you need this for your presentation layer / view model? In such case create specialized ViewModel
public class PostListView
{
public Post Post { get; set; }
public int CommentsCount { get; set; }
}
And use query with projection:
var data = context.Posts
.Select(p => new PostListView
{
Post = p,
CommentsCount = p.Comments.Count()
});
And you are done. If you need it you can flatten your PostListView so that it contains Post's properties instead of Post entity.
What about something like this:
public class PostView
{
public String PostName { get; set; }
public Int32 PostCount { get; set; }
}
public static IEnumerable<PostView> GetPosts()
{
var context = new PostsEntities();
IQueryable<PostView> query = from posts in context.Posts
select new PostView
{
PostName = posts.Title,
PostCount = posts.PostComments.Count()
};
return query;
}
Then use something like this:
foreach (PostView post in GetPosts())
{
Console.WriteLine(String.Format("Post Name: {0}, Post Count: {1}", post.PostName, post.PostCount));
}
Should display the list as so:
Post name (12)
Post name (1)
Etc etc