I'm stuck with a problematic, and not sure if this is the best way to solve it.
I've an Entity Category, which can have a list of sub categories ( child categories ) and has a Parent Category ( the only one without a parent category would be the "root" category. Is this the right way to do it ?
Category Class
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual IList<Category> ChildCategories { get; set; }
public virtual IList<Product> Products { get; set; }
}
Fluent API ModelBuilder
//Category Parent - Child
modelBuilder.Entity<Category>()
.HasOptional<Category>(category => category.ParentCategory)
.WithMany(category => category.ChildCategories)
.HasForeignKey(category => category.ParentId);
My seed method looks like this:
protected override void Seed(DAL.EF.MyDbContext context)
{
var catParent = new Category() {Name = "ParentCat"};
context.Categories.Add(catParent);
context.SaveChanges();
var catChild = new Category() { Name = "ChildCat", ParentId = catParent.Id};
context.SaveChanges();
catParent.ChildCategories.Add(catChild);
context.SaveChanges();
}
Now the thing is, In the seed I add 2 categories, 1 parent 1 child. When I debug to check if the structure is good, it's actually correct. The only thing that really bugs me is this structure:
ParentCategory -> ChildCategory -> ParentCategory.
As it is recursive, the child object will have a object "ParentCategory", but I actually only need the ID of it, not the whole object.
Is this normal or is there a fix for it?
Related
I have a one-to-many relationship... I am working in a web environment (disconnected environment). Imagine user wanting to update only the parent entity, without having to load all the child entities, is it possible?
This is the code:
public class Parent
{
public int Id { get; set; }
public string Description { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
public Parent Parent { get; set; }
public string Data { get; set; }
}
I want to update description of Parent with id = 5, the new description is coming from User:
Parent parent = new Parent()
{
Id = 5, // I already know the user Id
Description = "new description from User";
Children = null; // I don't want the children to be changed
}
dbContext.Parent.Attach(parent);
dbContext.Entry(parent).State = EntityState.Modified;
dbContext.SaveChanges();
I am not sure if this is the right approch? will existing Children be deleted (since the children list is null)?
is it possible?
Yes, you are doing right.
According to your sample
dbContext.Parent.Attach(parent);
dbContext.Entry(parent).State = EntityState.Modified;
dbContext.SaveChanges();
It just effects on parent table only.
Projecting self referencing multi level entities in Entity Framework 6.
Let's say that I have a Category entity as follows:
public class Category
{
public int CategoryId { get; set; }
public int? ParentCategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual ICollection<Category> SubCategories { get; set; }
public virtual ICollection<Product> Products { get; set; }
public Category()
{
SubCategories = new HashSet<Category>();
Products = new HashSet<Product>();
}
}
And I would like to map the whole Category DbSet with all the hierarchy to a following POCO class (while including all possible levels of sub and parent categories):
public class CategoryView
{
public int Id { get; set; }
public int? ParentCategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public CategoryView ParentCategory { get; set; }
public List<CategoryView> SubCategories { get; set; }
public int ProductCount { get; set; }
public Category()
{
SubCategories = new HashSet<CategoryView>();
}
}
Please bear in mind that a single category may have unlimited levels of subcategories as follows:
Category (Level 0)
SubCategory1 (Level 1)
SubCategory2
SubCategory2SubCategory1 (Level 2)
SubCategory2SubCategory2
SubCategory2SubCategory2SubCategory1 (Level 3)
... (Level N)
SubCategory3
When tried to create hierarchy with recursive a method which tries to process every single categories sub and parent categories, got stackoverflow exception, since it get stuck between the first category (Category) and the first subcategory (SubCategory1) due to relation between ParentCategory and SubCategories.
What is the best and elegant way of doing such projection (without eliminating parents)? (Or is there any?)
Any help would be appreciated.
Thank you,
I can't say if it's the best or elegant way, but it's pretty standard and efficient non recursive way of building such structure.
Start with loading all categories without parent / child object links using a simple projection:
var allCategories = db.Categories
.Select(c => new CategoryView
{
Id = c.CategoryId,
ParentCategoryId = c.ParentCategoryId,
Name = c.Name,
Description = c.Description,
ProductCount = c.Products.Count()
})
.ToList();
then create a fast lookup data structure for finding CategoryView by Id:
var categoryById = allCategories.ToDictionary(c => c.Id);
then link the subcategories to their parents using the previously prepared data structures:
foreach (var category in allCategories.Where(c => c.ParentCategoryId != null))
{
category.ParentCategory = categoryById[category.ParentCategoryId.Value];
category.ParentCategory.SubCategories.Add(category);
}
At this point, the tree links are ready. Depending of your needs. either return the allCategories or the root categories if you need a real tree representation:
return allCategories.Where(c => c.ParentCategoryId == null);
P.S. Actually the allCategories list can be avoided, since categoryById.Values could serve the same purpose.
It might not be elegant, but a suitable solution is to have in your code a shared IDictionary<int, CategoryView>. When you are going to map an entity Category into a CategoryView check first if you have already created this object and set the reference stored in the dictionary instead of creating a CategoryView instance. When creating a new instance, store it in the dictionary. This is a way to take advantage of the primary key of your entity to avoid the infinite recursion issue in your code.
Also, notice that in your CategoryView object you shouldn't be referencing Category instances. Update it to reference CategoryView instances like this.
public class CategoryView
{
public int Id { get; set; }
public int? ParentCategoryId { get; set; }
// other properties ...
public CategoryView ParentCategory { get; set; }
public List<CategoryView> SubCategories { get; set; }
public int ProductCount { get; set; }
public CategoryView()
{
SubCategories = new List<CategoryView>();
}
}
I have been trying to work this out for a while now and can't find an answer that makes sense to me. The concept is very common, so I must be totally misunderstanding a basic concept.
If I have a recipe class that can be found in many recipe categories then I have;
public class Recipe
{
public int ID { get; set; }
public string Title { get; set; }
public int CategoryID { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public int ID { get; set; }
public string Description { get; set; }
public int RecipeID {get; set;
public virtual void Recipe Recipe {get; set;}
But I also need a join table that relates a recipe to a Category. I want to display this;
Recipe Title | Category
Mac-N-Cheese | Pasta
| Easy
Pot Roast | Beef
| Slow cooker
The Category is a table of available categories. So the join table has
RecipeID | CategoryID
I tried setting up the models using the Entity Framework format of foreign keys and navigation properties.
So I set up the join table like this;
public class RecipeCategories
{
public int ID { get; set; }
public int RecipeID { get; set; }
public int CategoryID { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
So a recipe can have many categories. The join table can have many recipes and many categories. The category table is just a simple list of categories. What am I missing? When I try to run the view there is no list of categories for the given recipe. The best I have achieved is the CategoryID.
Sorry for the long post, but you need all the details.
The problem is your Category model. The way you've currently defined it you have a one-to-many relationship between Category and Recipe (a Recipe can have many Categories but a Category one has a single Recipe). What you want is a many-to-many relationship so put a collection of Recipes on the Category and EF should automatically generate the join table.
public class Recipe
{
public int ID { get; set; }
public string Title { get; set; }
public int CategoryID { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public int ID { get; set; }
public string Description { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
}
I am no getting actual scenario as you trying to achieve.
as you mention your model and then what you expected both contradict. as per you mention you have recipe and category (one to many) but later you change your model and mention you want (many to many) so you need join table. as a relation model you can handle 2 ways in EF. 1st without creating separate table and keep collection map to each model and 2nd way creating separate table and explicitly map that in your model. you need to explicitly specify while model building.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Recipe>()
.HasMany(c => c.Categories).WithMany(i => i.Recipes)
.Map(t => t.MapLeftKey("RecipeID")
.MapRightKey("CategoryID")
.ToTable("ReceipeCategory")
);
you can define your class model (and third table will generate but don't required specific model )
class Recipe {.... public virtual ICollection<Category> Categories { get; set; }
class Category {... public virtual ICollection<Recipe> Recipes { get; set; } }
I have Category that can have RootCategory. Problem is that it does not set RootCategoryID properly, instead it is creating Category_ID in database that I don't even made in my model.
It does map everything like I expect it to if I don't have modified get on RootCategory by the way. But then RootCategory is always null (he does not know where to get it from)
Model
public class Category
{
public int ID { get; set; }
// Tried [ForeignKey("RootCategoryID")]
public Category RootCategory {
get
{
ORDataContext _db = new ORDataContext();
return _db.Categories.Where(x => x.ID == this.RootCategoryID).SingleOrDefault();
}
}
public int? RootCategoryID { get; set; } // This does not set itself properly
public ICollection<Category> ChildCategories { get; set; }
}
Generated database after update-database
-ID
-RootCategoryID (that I have created, but it's not used)
-Category_ID (taht EF created for me, that I don't want)
You don't need to manuallyload the nav property RootCategory like you have, EF will do it automatically. However, EF is having trouble inferring what you're wanting, you should map it explicitly either with data annotations:
public class Category
{
public int ID { get; set; }
public virtual Category RootCategory { get; set; }
[ForeignKey("RootCategory")]
public int? RootCategoryID { get; set; } // This does not set itself properly
public virtual ICollection<Category> ChildCategories { get; set; }
}
or via fluent:
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
modelBuilder.Entity<Category>()
.HasMany( c => c.ChildCategories )
.WithOptional( ca => ca.RootCategory )
.HasForeignKey( c => c.RootCategoryID );
}
And all your properties/collections should work.
I have two domain models in my project: Category and Sub-category. This is my one to many relationship and how I created it:
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public List<SubCategory> SubCategories { get; set; }
}
public class SubCategory
{
public int SubCategoryId { get; set; }
public string SubCategoryName { get; set; }
public Category Category { get; set; }
}
Now I am not sure how to add a Sub-Category to a Category, but I want the association to be kept for each Sub-Categories in a Category. So I tried passing a Category to my CreateSubCategory View
public ActionResult CreateSubCategory(int categoryId)
{
return View(_service.GetCategory(categoryId));
}
I came to here in my view and got stumped
#using (Html.BeginForm()){
<h2>SubCategory</h2>
<div>#Html.DisplayFor(model => model.CategoryName)</div>
<div>#*Want to create Sub-category here*#</div>
<p><input type="submit" value="Create" /></p>
}
I guess this is a noob question but I cannot figure out what to do here.
So is there any advice on how to do this, or any way of performing this task of adding my Sub-categories to Categories?
First of all, add the virtual keyword.
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public virtual ICollection<SubCategory> SubCategories { get; set; }
}
// add constructor here for category, and set SubCatogories to new HashSet();
public class SubCategory
{
public int SubCategoryId { get; set; }
public string SubCategoryName { get; set; }
public int CategoryId { get; set; }// add this:)
public virtual Category Category { get; set; }
}
now there are two ways to add a subcategory;
either get an instance of a Category and then add an instance of SubCategory to its SubCategories collection, or create a new SubCategory and set its fields including the CategoryId and then add it to its corresponding DbSet.
If you're creating a SubCategory, you should be passing a SubCategory (or SubCategoryViewModel) object as the model to your View.
You can create a new instance of the SubCategory in your CreateSubCategory() method and assign the CategoryId to the new object (as hazimdikenli pointed out). Be sure to store the Id using #Html.HiddenFor(m => m.CategoryId) so that it's persisted for the returning Post method.
public ActionResult CreateSubCategory(int categoryId)
{
SubCategory model = new SubCategory();
model.CategoryId = categoryId;
return View(model);
}
When the object is posted back, that is the point where you commit to the database, saving the new object and creating the association with the parent Category object (via the Id reference).
If you want to pass the CategoryName, you should probably create a view model with a CategoryName string property (this would of course need to be populated in the CreateSubCategory() method)
It may be simpler to use a single domain model, especially if subcategories can have their own subcategories:
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual ICollection<Category> ChildCategories { get; set; }
}
Then configure the relationship in the OnModelCreating method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.Entity<Category>()
.HasMany(x => x.ChildCategories)
.WithOptional(x => x.ParentCategory);
}
To create a new subcategory simply create a new Category and set its ParentCategory before sending the subcategory to your view.