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
Related
I have the following Entities on Entity Framework Core 2.2:
public class Post {
public Int32 Id { get; set; }
public string Type { get; set; }
public virtual Collection<File> Files { get; set; }
}
public class File {
public Int32 Id { get; set; }
public Int32 PostId { get; set; }
public String Name { get; set; }
public Byte[] Content { get; set; }
public virtual Post Post { get; set; }
}
I need to get the list of files Ids and Names of a Post without loading their Content into Memory.
IQueryable<Post> posts = _context.Posts.AsNoTracking();
posts = posts.Include(x => x.File);
var files = await posts
.Where(x => x.Type == "design")
// Remaining Query
I think the moment I use Include the files will be loaded into memory. No?
What is the correct way to get a list of Posts' Files Ids and Names without loading their Content into Memory?
I need to get the list of files Ids and Names of a Post without loading their Content into Memory.
What is the correct way to get a list of Posts' Files Ids and Names without loading their Content into Memory?
Once you said you want to get a Post and then said you want to get a list of Post.
So to get a Post with its files (only Id and Name) you can write your query as follows:
var post = await _context.Posts.Where(yourCondition).Select(p => new
{
p.Id,
p.Type
Files = p.Files.Select(f => new {f.Id,f.Name}).ToList()
}).FirstOrDefaultAsync();
And to get list of Posts with its files (only Id and Name) you can write your query as follows:
var posts = await _context.Posts.Where(yourCondition).Select(p => new
{
p.Id,
p.Type
Files = p.Files.Select(f => new {f.Id,f.Name}).ToList()
}).ToListAsync();
Note: If you need strongly typed then can write as follows:
Post post = await _context.Posts.Where(yourCondition).Select(p => new Post
{
Id = p.Id,
Type = p.Type
Files = p.Files.Select(f => new File {f.Id,f.Name}).ToList()
}).FirstOrDefaultAsync();
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);
}
Let's say I have 3 models:
[Table("UserProfile")]
public class UserProfile //this is a standard class from MVC4 Internet template
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public int UserProfileId { get; set; }
[ForeignKey("UserProfileId")]
public virtual UserProfile UserProfile { get; set; }
}
Now, I'm trying to edit Post
[HttpPost]
public ActionResult Edit(Post post)
{
post.UserProfileId = context.UserProfile.Where(p => p.UserName == User.Identity.Name).Select(p => p.UserId).FirstOrDefault();
//I have to populate post.Category manually
//post.Category = context.Category.Where(p => p.Id == post.CategoryId).Select(p => p).FirstOrDefault();
if (ModelState.IsValid)
{
context.Entry(post.Category).State = EntityState.Modified; //Exception
context.Entry(post.UserProfile).State = EntityState.Modified;
context.Entry(post).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
And I'm getting ArgumentNullException.
Quick look into debug and I can tell that my Category is null, although CategoryId is set to proper value.
That commented out, nasty-looking trick solves this problem, but I suppose it shouldn't be there at all. So the question is how to solve it properly.
I would say it's something with EF lazy-loading, beacuse I have very similar code for adding Post and in debug there is same scenerio: proper CategoryId, Category is null and despite of that EF automagically resolves that Post <-> Category dependency, I don't have to use any additional tricks.
On edit method, EF has some problem with it, but I cannot figure out what I'm doing wrong.
This is working as intended. Your Post object is not attached to the Context, so it has no reason to do any lazy loading. Is this the full code? I don't understand why you need to set Category as Modified since you're not actually changing anything about it.
Anyway, I recommend you query for the existing post from the Database and assign the relevant fields you want to let the user modify, like such:
[HttpPost]
public ActionResult Edit(Post post)
{
var existingPost = context.Posts
.Where(p => p.Id == post.Id)
.SingleOrDetault();
if (existingPost == null)
throw new HttpException(); // Or whatever you wanna do, since the user send you a bad post ID
if (ModelState.IsValid)
{
// Now assign the values the user is allowed to change
existingPost.SomeProperty = post.SomeProperty;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
This way you also make sure that the post the user is trying to edit actually exists. Just because you received some parameters to your Action, doesn't mean they're valid or that the post's Id is real. For example, some ill intended user could decide to edit posts he's not allowed to edit. You need to check for this sort of thing.
UPDATE
On a side note, you can also avoid manually querying for the current user's Id. If you're using Simple Membership you can get the current user's id with WebSecurity.CurrentUserId.
If you're using Forms Authentication you can do Membership.GetUser().ProviderUserKey.
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);
I have a parent object book, and a property of that object is publisher. Everytime I ad a book, it is adding a new publisher, even if the publisher already exists. Can someone tell me how to add the book and instead of adding the publisher again, just reference an existing one? The code i am using is below... Thanks in advance!
public class Book
{
public int BookID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime CreateDate { get; set; }
public virtual Publisher Publisher { get; set; }
}
public class Publisher
{
public int PublisherID { get; set; }
public string Address { get; set; }
}
public class SqlCEDataStore : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Publishers> Publishers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.IncludeMetadataInDatabase = false;
}
}
public class TimeSinkRepository : IRepository<Book>
{
private static SqlCEDataStore context = new SqlCEDataStore();
public int Add(Book entity)
{
context.Books.Add(entity);
return context.SaveChanges();
}
}
var book = new Book()
{
Title = "New Title",
Description = "New Description",
CreateDate = DateTime.Now,
Publisher = new Publisher() { PublisherID = 1 }
};
var repository = new BookRepository();
var result = repository.Add(book);
The problem is in the line:
Publisher = new Publisher() { PublisherID = 1 }
Object context doesn't know that this is existing publisher. It is newly created entity so Object context will perform insert operation. You have to say object context that the publisher object is not newly created. One way to do that is modification of your Add method:
public int Add(Book entity)
{
context.Books.Add(entity);
// 0 means new one, other values mean existing one
if (entity.Publisher.PublisherID > 0)
{
context.ObjectStateManager.ChangeObjectState(entity.Publisher, EntityState.Unchanged);
}
context.SaveChanges();
}
It you can solve this by making sure the Publisher is attached to Publishers context before adding the Book entity (this way it knows it's a Publisher from the dbcontext and not a new one that it needs to add (again))
context.Publishers.Attach(book.Publisher); // This is only possible if the Publisher is not new
context.Books.Add(book);
the problem is in this line
Publisher = new Publisher() { PublisherID = 1 }
You should do a fetch method so something like this
- Get the Publisher you want from the context (eg where id = 1)
- Set the returned object as the publisher for your new book object
- The context should sort the rest out for you. when you save the book. (no need to mess with the object state manager)
Good luck, if you cant get this working put up some code of it and i will help you though it.