Entity Framework - Relationships with Where - asp.net-mvc

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

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

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.

Related

Updating many-to-many relationship entity framework

I have problem with updating entites that have many-to many relationship. Below my User and category class:
public class User : IEntity
{
[Key]
public virtual long Id { get; set; }
private ICollection<Category> _availableCategories;
public virtual ICollection<Category> AvailableCategories
{
get { return _availableCategories ?? (_availableCategories = new List<Category>()); }
set { _availableCategories = value; }
}
}
public class Category : IEntity
{
[Key]
public long Id { get; set; }
/// <summary>
/// Full name or description of a category
/// </summary>
[StringLength(255)]
public string FullName { get; set; }
}
This is code snippet from my repository
public override void Edit(User user)
{
var dbUser = _context.Users.Include(x => x.AvailableCategories)
.Single(x => x.Id == user.Id);
var categories = _context.Categories;
dbUser.AvailableCategories.Clear();
foreach (var cat in user.AvailableCategories)
{
dbUser.AvailableCategories.Add(cat);
}
_context.Entry(dbUser).State = EntityState.Modified;
}
However the categories don't get updated. What EF does is insert empty rows into category table and sets relations to this new rows with user.
How can I update User so that I change only categories that already exist in the database?
User that I pass to Edit method has AvailableCategories with only Ids set (rest of properties are empty).
When you're doing something like posting back M2M relationships, you either must post the full object, as in every single property on those objects, or simply post a list of ids and then use those to query the associated objects back from the database. Otherwise, Entity Framework understands your purpose to be to update the properties on the objects as well, in this case with empty values.
Obviously the first option is quite unwieldy, so the second way is the preferred and standard way. Generally, for this, you'd want to use a view model so you could have a property like the following, that you would post into:
public List<long> SelectedCategories { get; set; }
But, if you insist on using the entity directly, you can get much the same result by simply doing:
var selectedCategories = user.AvailableCategories.Select(m => m.Id)
Once you have the ids:
var newAvailableCategories = _context.Categories.Where(m => selectedCategories.Contains(m.Id));
And then finally set that on your user:
dbUser.AvailableCategories = newAvailableCategories;
I notice you are also adding the user.AvailableCategories directly into dbUser.AvailableCategories. I've noticed when binding back complex objects from an MVC view that DB Entities are no longer attached to the DbContext. If you look at the entity, you can verify by checking dbContext.Entry(cat).State is "detached" (or something unexpected) I believe.
You must query those entities back out of the dbContext (possibly by using the returned cat.Id's). Or otherwise manually set the entities as "unchanged". And then add those "non-detached" items into dbUser.AvailableCategories. Please see Chris's answer as it shows with specific code how to get this done.
Also, I might use a linking entity. Possibly something like this:
public class UserCategory
{
public User User {get;set;}
public Category Category {get;set;}
}
And add it to DB context. Also, drop the linking lists in your current User and Category class. This way you can manipulate the UserCategory class (and DbSet) to manage your many-to-many relationship.

EF5 with Many to Many Relationship creates cyclic reference when serializing to JSON

I have an Entity Framework 5 Code First model with a many to many relationship
i.e.
Public class Product
{
public int ProductId {get;set;}
public ICollection<Category> Categories {get;set;}
}
Public class Category
{
public int CategoryId {get;set;}
public ICollection<Product> Products {get;set;}
}
I'm creating the actual relation in fluent, thus;
modelBuilder.Entity<Product>()
.HasMany(p => p.Categories)
.WithMany(c => c.Products)
.Map(m =>
{
m.ToTable("ProductsToCategories");
m.MapLeftKey("Products_ProductId");
m.MapRightKey("ProductCategories_ProductCategoryId");
});
Now when I retrieve my data is is retrieving Product, and Product as a bunch of Categories, BUT each Category also has a bunch of products in it as well, and so it recurses around.
The problem is that this is causing havoc when I then serialise it to JSON for use by the front end (I'm using KnockOut, but that is kind of irrelevant).
I've tried turning Lazy loading off, and when I get my products I use an include;
db.Products.Include("Categories").ToList()
but this is still then performing the recursive gathering of products within each Category.
Any ideas?
Regards
You can also use business objects instead of using database objects directly. In this manner you only reference from on side, say :
Public class Product
{
public int ProductId {get;set;}
public IList<Category> Categories {get;set;}
}
Public class Category
{
public int CategoryId {get;set;}
}
You have reference cyclic between Product and Category.
In other words Product have a relation to Category and Category have a relation to Product.
So, what you need to do is to delete one of these relations.
I would do someting like that :
var categoriesGraph = db.Categories.Include("Products").ToList();
var data = categoriesGraph.Select(c => new
{
c.CategoryId,
Products = c.Products.Select(p = > new {
p.ProductId,
CategoriesID = p.Categories.Select(c => c.CategoryId).ToArray(),
// don't add the categories.
}).ToArray()
}).ToArray();
I hope it helps.
You can tell Json.Net to just ignore circular references:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
If you want references to existing data, then you look at fix 2 in this article

Two one to many association on a LinkTable

I have a LinkTable named: BlogsBlogPosts that relates two other tables named: Blogs and BlogPosts together, I want the link table to appear in the list of entities and There will be two one to many relationships defined on the linktable am I correct to define it like this?:
public class BlogsBlogPostConfiguration : EntityTypeConfiguration<BlogsBlogPost>
{
public BlogsBlogPostConfiguration()
{
this.HasRequired(bb => bb.Blog)
.WithMany(b => b.BlogsBlogPosts)
.HasForeignKey(bb =>bb.BlogID);
this.HasRequired(bb => bb.BlogPost)
.WithMany(bp => bp.BlogsBlogPosts)
.HasForeignKey(bb => bb.BlogPostID);
}
}
Yes. The only question which remains for me is: What about the primary key property of BlogsBlogPost? Two options:
You either have a distinct key property like public int BlogsBlogPostID { get; set; } or public int ID { get; set; } in this entity. In this case combinations of (BlogID, BlogPostID) don't have to be unique.
Or you have a composite key consisting of (BlogID, BlogPostID). You would have to mark this with the [Key] annotation or define it in Fluent API:
this.HasKey(bbp => new { bbp.BlogID, bbp.BlogPostID });
Now combinations of (BlogID, BlogPostID) must be unique. This is more of a "many-to-many relationship with additional data in the link table" type of relationship.
Both models are possible. Which to use depends on your business needs.

EF4: Save object children

in database I have these tables: Product, Request and RequestProducts.
RequestProducts is a table to link many Product to one Request.
Here is my code:
Product newProduct = new Product
{
Unity_ID = 3,
Quantity = 2,
Name = "toto",
AlreadyCurrency = true
};
Request newRequest = new Request
{
User_ID = 1,
CaseNumber = 1,
Draft = false
};
newRequest.Products.Add(newProduct);
_db.AddToProducts(newProduct);
_db.AddToRequests(newRequest);
_db.SaveChanges();
After execute that, in my database I get 1 product and 1 request. It's ok, but the link create with line newRequest.Products.Add(newProduct); is not created the table RequestProducts and yet EF4 understand the link by propose me the Products list in the Request object.
Is it possible to create this link only with this code?
Thank you!
You don't need an extra table for a one-to-many relationship. You'd only need that extra table for many-to-many relationships.
So, without knowing more specifics about what you're doing and why, I'd say it's working properly.
Ideally, your Request object would look something like:
public class Request
{
public int RequestId {get; set;}
//Other defining params here
public ICollection<Product> Products {get; set;}
}
Then your Product object would look like:
public class Product
{
public int ProductId {get; set;}
//other defining params
public int RequestId {get; set;} //your FK
public virtual Request Request {get; set;} //If you ever need the Product to be aware of the Request to which it is attached.
}
Assuming you use generated entities of EDMX, you should have a single RequestId FK column in your Product table.
Then after you generate the EDMX from DB, there will not be additional tables, so you should then be able to do it this way:
Request newRequest = new Request
{
User_ID = 1,
CaseNumber = 1,
Draft = false
};
Product newProduct = new Product
{
Unity_ID = 3,
Quantity = 2,
Name = "toto",
AlreadyCurrency = true,
Request = newRequest
};
//Redundant
//newRequest.Products.Add(newProduct);
_db.AddToProducts(newProduct);
//Redundant
_db.AddToRequests(newRequest);
_db.SaveChanges();
//Never forget - or wrap in a using statement
_db.Dispose()
Update
The case above is one2many (one request has many products)
becuase as soon as I set the Request (which is now a property of the Product) to newRequest, then when I add it to the context (or if it's already added), all the related entities of the whole graph will automatically be added too.
Read this carefully.

How to Create a Run-Time Computed (NotMapped) Value in Entity Framework Code First

I have a user class in EF Code First that contains a lot of properties, and each user has a collection of "Contacts" which are other users as a subset of the total user population. The other collection "ContactOfOthers" is just the reverse showing who has this user as a contact as this is a many-to-many relationship.
public class User {
[Key]
public string UserName { get; set; }
// Lots of other properties not relevant to this question...
[NotMapped]
public bool IsMyContact { get; set; }
public virtual ICollection<User> Contacts { get; set; }
public virtual ICollection<User> ContactOfOthers { get; set; }
}
I introduced a not-mapped (not mapped to DB) property called IsMyContact. This is for cases when the user queries for a bunch of users and I need to show in the View which users are already in their contacts list. So this property should be true if the User is part of their "Contacts" collection. It shouldn't be saved to the DB since it can be different for the same user, depending on the user doing the query.
Is there a nice way to do this in a query from the context? It could of course be brute-forced by doing two queries then iterating through the main one, looking for matches to the user's Contacts collection, but I'm wondering if there's a more elegant way to do this from one query, projecting a run-time computed column onto this property?
I don't know a way how to populate the IsMyContact property in the User directly within the query. But an alternative approach could be to introduce a ViewModel which wraps the User and has in addition the IsMyContact flag:
public class UserViewModel
{
public bool IsMyContact { get; set; }
public User User { get; set; }
}
(The class User would not have the IsMyContact flag anymore.)
You could then project into this type when you run your query:
string me = "That's me"; // name of the user who is selecting
List<UserViewModel> list = context.Users
.Where(u => ...some filter...)
.Select(u => new UserViewModel
{
IsMyContact = u.ContactOfOthers.Any(c => c.UserName == me),
User = u
})
.ToList();
The benefits would be: You need only one round trip and you are not forced to load the whole collection of Contacts to determine the IsMyContactFlag (but you can if you want to).
The drawback: You need this additional ViewModel type.
It is possible to do this but it will be far from a "nice way" because you cannot return instances of your User type. You must write custom linq-to-entities query and you must solve two problems:
You cannot project to mapped types in linq-to-entities
You cannot access non mapped properties in linq-to-entities
So my high level untested idea about doing this is:
var query = from u in ctx.Users
where u.Id != id // don't include current user - you can add other condition
join c in ctx.Users
.Where(x => x.Id == id) // current user
.SelectMany(x => x.Contacts)
on u.Id equals c.Id into leftJoin
from y in leftJoin.DefaultIfEmpty()
select new
{
UserName = u.UserName,
IsMyContact = y != null
};
This should be a query which will load pairs of UserName and information if the user is contact or not. If you want User instance instead you must do something like this:
var users = query.AsEnumerable()
.Select(new User
{
// Project to list in linq-to-objects
});

Resources