ishanmeh ishanmeh - 1 month ago 8
C# Question

Entity Framework adds unnecessary record on SaveChanges()

I am still learning entity framework with MVC and trying to create a generic shopping site. I have models - Category and Products having one-to-many relationship (for example: Product "IPhone7" will belong to Category "Electronics") See Models edmx diagram here

The problem is whenever I add a new Product, say P, belonging to category C, EF automatically adds a new entry C to Category table, even if C already exists in Category table. I believe issue is the way EF handles one-to-many relationship. I have also tried adding [Index(IsUnique = true)] to categoryName in Category class, but it did not help. I do not want EF to add a new category every time a product is added.

Below is the code snippet of my controller, view, data layer and business layer.

//Controller
public ActionResult SaveCategory(Category c)
{
AdminBusinessLayer adminBL = new AdminBusinessLayer();
adminBL.AddCategory(c);
return RedirectToAction("Index");
}

public ActionResult SaveProduct(Product p, string btnSubmit, string dpdCategories)
{
AdminBusinessLayer adminBL = new AdminBusinessLayer();
Category productCategory = new Category();
List<Category> categoryList = new List<Category>();
categoryList = adminBL.ShowCategory();
foreach (Category c in categoryList)
{
if (c.CategoryName == dpdCategories)
productCategory = c;
}
p.category = productCategory;
p.CategoryId = productCategory.Id;
adminBL.AddProduct(p);
return RedirectToAction("Index");
}




@{
ViewBag.Title = "Admin Functions";
}

@using Flipkart.ViewModels
@using Flipkart.Models
@model AdminViewModel

<form action="/Admin/SaveCategory" method="post">
<h2>Add New Category</h2>
Name: <input type="text" id="txtCategoryName" name="CategoryName" />
<br />
<br />
<input type="submit" name="btnAddCategory" value="Add Category" />
<br />
<br />
<h2>Existing Categories</h2>
<table border="1" cellpadding="5" width="20">
<tr style="background-color:yellowgreen">
<th>Id</th>
<th>Category</th>
</tr>
@foreach (Category c in Model.categoryList)
{
<tr>
<td>@c.Id</td>
<td>@c.CategoryName</td>
</tr>
}
</table>
</form>
<br />
<br />
<br />
<br />
<form action="/Admin/SaveProduct" method="post">
<h2>Add New Product</h2>
Name: <input type="text" id="txtProductName" name="ProductName" />
Category: @Html.DropDownList("dpdCategories",new SelectList(Model.categoryNames,Model))
Price: @Html.TextBox("Price")
<br />
<br />
<input type="submit" name="btnAddProduct" value="Add Product" />
<br />
<br />
<br />
<br />
<h2>Existing Products</h2>
<table border="1" cellpadding="5" width="20">
<tr style="background-color:yellowgreen">
<th>Id</th>
<th>Product</th>
</tr>
@foreach (Product p in Model.productList)
{
<tr>
<td>@p.Id</td>
<td>@p.ProductName</td>
</tr>
}
</table>
</form>





//Data Layer
public class FlipkartContext:DbContext
{
public FlipkartContext() : base("Flipkart")
{
}
public DbSet<Category> Category { get; set; }
public DbSet<Product> Product { get; set; }
public DbSet<Cart> cart { get; set; }
}


Below is the business layer that saves data to database:

//Business Layer
public void AddCategory(Category c)
{
FlipkartContext flipkartDBContext = new FlipkartContext();
flipkartDBContext.Category.Add(c);
flipkartDBContext.SaveChanges();
}

public List<Category> ShowCategory()
{
List<Category> categoryList = new List<Category>();
FlipkartContext flipkartDBContext = new FlipkartContext();
categoryList = flipkartDBContext.Category.ToList();
return categoryList;
}
public void AddProduct(Product p)
{
FlipkartContext flipkartDBContext = new FlipkartContext();
flipkartDBContext.Product.Add(p);
flipkartDBContext.SaveChanges();
}

public List<Product> ShowProduct()
{
List<Product> productList = new List<Product>();
FlipkartContext flipkartDBContext = new FlipkartContext();
productList = flipkartDBContext.Product.ToList();
return productList;
}

Answer

The problem is that in each request in your bussiness layer you actually create new context of EF. This means when you fetch data by ShowCategory method EF does not track changes of returned Category object when you put it into AddProduct method.

Usually EF context is shared for the whole one request to the server. If you just use one context categories will not be duplicated.

you should just put private field into your bussiness layer class

private FlipkartContext _flipkartDBContext;

and initialise it in constructor of that class

_flipkartDBContext = new FlipkartContext();

and then you can just use that one instance of it in whole class

Comments