-2

Following this answer I'm trying to do :

var posts = await _context.Posts
            .Select(p => new 
            { 
                p,
                Comments = p.Comments
                .Where(c.Status == "approved")
            })
            .Include(p => p.Author)
            .Include(p => p.Comments)
            .SingleOrDefaultAsync(m => m.Id == id);

In order to get only "approved" comments with my post.

But I get the following error :

The name 'c' does not exist in the current context [ASPress]

So there's something I don't get right with the syntax

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Pierre
  • 4,976
  • 12
  • 54
  • 76

2 Answers2

3

You have to define the identifier you're using in the lambda...

.Where(c => c.Status == "approved")

should fix it.


Having just read your comments, I believe it is based on the order you are using the LINQ methods. From Posts you are projecting to an anonymous type and that won't have what you want. Try to re-arrange it so it reads as follows:

var posts = await _context.Posts
      .Include(p => p.Author)
      .Include(p => p.Comments)
      .Select(p => new
      {
          p,
          Comments = p.Comments
            .Where(c => c.Status == "approved")
      })
Zoe
  • 27,060
  • 21
  • 118
  • 148
Fabulous
  • 2,393
  • 2
  • 20
  • 27
  • I do not have the error on the "c" anymore, but now the .Include(p => p.Author) is raising an error : ' Comments>' does not contain a definition for 'Author' and no extension method 'Author' accepting a first argument of type ' Comments>' could be found (are you missing a using directive or an assembly reference?) [ASPress] – Pierre Jan 06 '18 at 17:14
  • Hi @Piero, I've just edited my answer. See if it addresses your problem now. I'll review it a little later when I get to a PC with Visual Studio to see if the syntax is 100% correct. – Fabulous Jan 06 '18 at 20:43
  • Hi and thanks for following up. The application cannot compile beacause I have the following error on the .SingleOrDefaultAsync(m => m.Id == id); : ' Comments>' does not contain a definition for 'Id' and no extension method 'Id' accepting a first argument of type ' Comments>' could be found (are you missing a using directive or an assembly reference?) [ASPress] – Pierre Jan 08 '18 at 06:21
  • I may need to see the table schema for a little more context to see if I can refactor the linq statement. I think you can insert a ```.Where(m => m.Id == id)``` before the ```Select```, then do a ```SingleOrDefault()``` on the end result without parameters to see if that will help. – Fabulous Jan 08 '18 at 10:01
1

Although you forgot to write your classes, it seems to me that you have a sequence of Posts. A Post has exactly one Author. Every Post has zero or more comments, and every comment belongs to exactly one Post: there are no comments that belong to nothing, there is a true one-to-many relation between Posts and Comments.

If you follow the guidelines for a one-to-many configuration, you'll have something similar to the following:

class Author
{
    public int Id {get; set;}

    // not certain, but very likely: every Author has zero or more Posts:
    public virtual ICollection<Post> Posts {get; set;}
}
class Post
{
    public int Id {get; set;}

    // Every Post is posted by exactly one Author, using foreign key
    public int AuthorId {get; set;}
    public virtual Author Author {get; set;}

    // Every Post has zero or more Comments:
    public virtual ICollection<Comment> Comments {get; set;}
}
class Comment
{
    public int Id {get; set;}

    // every Comment belongs to exactly one Post using foreign key
    public int PostId {get; set;}
    public virtual Post Post {get; set;}

    public string Status {get; set;}
}

Because I followed the entity framework naming conventions, this is enough to tell entity framework the primary keys, the foreign keys and the one-to-many relations. If you decide to deviate from the naming conventions, you'll probably need some attributes of fluent API, but the idea remains the same.

Back to your question

It seems to me that you want all Posts, together with their Author and only their approved Comments.

You use Include statements to get the data from the database. This is usually a waste of processing power, because you probably won't use all Post properties and all Comment Properties. It is better to explicitly name the Properties you plan to use.

var result = myDbcontext.Posts.Select(post => new
{
    // query only the attributes you plan to use, for example:
    Id = post.Id,
    Title = post.Title,

    Author = new
    {
        Id = post.Author.Id,
        Name = post.Author.Name,
        // no need to query the foreign Key Author.PostId, I already got that one in property Id
        ... // other author properties you need
    },

    Comments = post.Comments
        .Where(comment => comment.Status == "approved")
        .Select(comment => new
        {   // again: query only the properties you plan to use:
            Text = comment.Text,
            ...
        }
        .ToList(),

    ... 
};

In my experience, if you configure your one-to-many relations properly you seldom need a join. Use the ICollections instead. Entity Framework will know that a join is needed for this.

However you can get the same results using a join:

var result = myDbContext.Posts
    .Join(myDbContext.Comments.Where(comment => comment.Status = "approved"),
     post => post.Id,              // from every Post take the Id
     comment => comment.PostId     // from every Comment take the PostId
     (post, comment) = new         // when they match make a new object
     {
         Id = post.Id,
         Title = post.Title,

         CommentText = comment.Text,
      }

etc. Note this is a real inner join. If you want the "Post with its comments" you'll need to do a GroupBy Id

Finally: if you only have a limited number of states, consider creating an enum for your status. This will prevent people from assigning "Approved" to the state instead of "approved", or worse: errors like "approved?" Internally in your database they will still be strings, but entity framework will parse them to proper enum values and you won't be able to put improper values in the database.

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • Thanks for the explanation, I'm going to try this. I'm completely new to the EntityFramework... I'm used to the PHP Eloquent ORM, and it's just a breeze to do this kind of stuff so I'm having a hard time getting the proper way to do this. Separate the comments query seems to be a good way, I'm just surprised it's not simpler to add a condition on the comments... – Pierre Jan 08 '18 at 21:45
  • 1
    A good starting point to learn entity framework. Takes a few hours to get all basics, inclusive one-to-many, many-to-many, inheritance, composition, fluent api, database initialisation is the following link: http://www.entityframeworktutorial.net/code-first/entity-framework-code-first.aspx (now I need to learn how to add a link in a comment) – Harald Coppoolse Jan 09 '18 at 08:03