hesam hesam - 2 months ago 30
C# Question

Insert/Update many to many with Generic Repository

I have problem with insert and update by Generic Repository, in generic insert or update it hasnot problem but in many to many relation i am getting error at insert :

An entity object cannot be referenced by multiple instances of IEntityChangeTracker.

update:

The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.

my codes are

Interface

public interface IGenericRepository<TEntity>:IDisposable
{
void Insert(TEntity entity);
void Update(TEntity entity);
}


Generic class

public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
private ApplicationDbContext context=null;
private DbSet<TEntity> dbSet=null;
public GenericRepository()
{
this.context = new ApplicationDbContext();
this.dbSet = context.Set<TEntity>();
}
public GenericRepository(ApplicationDbContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}

public virtual void Insert(TEntity entity)
{
Error is here---> this.context.Set<TEntity>().Add(entity);
// dbSet.Add(entity);
context.SaveChanges();
}

public virtual void Update(TEntity entity)
{
Error is here---> dbSet.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
}

}


and control codes are

private IGenericRepository<Blog> _Repository = null;
private IGenericRepository<BlogTag> _RepositoryTag = null;
private IGenericRepository<BlogCategory> _RepositoryCategory = null;

public BlogsController()
{

this._Repository = new GenericRepository<Blog>(new DbContext());
this._RepositoryTag = new GenericRepository<BlogTag>(new DbContext());
this._RepositoryCategory = new GenericRepository<BlogCategory>(new DbContext());
}

public async Task<ActionResult> Create([Bind(Include = "BlogID,BlogTitle,BlogContent,VisitCount,Preview")] Blog blog
,string[] SelectedTags,string[] SelectedCategories, HttpPostedFileBase files)
{

if (SelectedTags != null)
{
blog.BlogTags = new List<BlogTag>();
foreach (var tag in SelectedTags)
{
var tagToAdd = _RepositoryTag.GetById(int.Parse(tag));
blog.BlogTags.Add(tagToAdd);
}
}
if (SelectedCategories != null)
{
blog.BlogCategories = new List<BlogCategory>();
foreach (var cat in SelectedCategories)
{
var catToAdd = _RepositoryCategory.GetById(int.Parse(cat));
blog.BlogCategories.Add(catToAdd);
}
}

if (ModelState.IsValid)
{
blog.DateTimeInsert = DateTime.UtcNow;
blog.DateTimeModify = DateTime.UtcNow;
blog.ImagePath= files != null ? Path.GetFileName(files.FileName) : "";
blog.BlogContent = HttpUtility.HtmlEncode(blog.BlogContent);

_Repository.Insert(blog);

return RedirectToAction("Index");
}

ViewBag.BlogTags = new SelectList(_RepositoryTag.Get(), "BlogTagID", "TagName");
ViewBag.BlogCategories = new SelectList(_RepositoryCategory.Get(), "BlogCategoryID", "CategoriesName");

return View(blog);
}


[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "BlogID,BlogTitle,BlogContent,VisitCount,Preview")] Blog blog
, string[] SelectedTags, string[] SelectedCategories, HttpPostedFileBase files)
{

if (Request["BlogID"] == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
int id = int.Parse(Request["BlogID"].ToString());
var blogsToUpdate = _Repository.Query(i => i.BlogID == id, null).Include(t => t.BlogTags).Include(t => t.BlogCategories).Single();

if (TryUpdateModel(blogsToUpdate, "",
new string[] { "BlogID", "BlogTitle", "BlogContent", "VisitCount","Preview" }))
{
try
{


UpdateInstructorCourses(SelectedTags, SelectedCategories, blogsToUpdate);

blogsToUpdate.DateTimeModify = DateTime.UtcNow;
blogsToUpdate.DateTimeInsert = DateTime.UtcNow;
blogsToUpdate.BlogContent = HttpUtility.HtmlEncode(blogsToUpdate.BlogContent);

await _Repository.UpdateAsync(blogsToUpdate, d => d.BlogTitle, d => d.VisitCount, d => d.BlogContent, d => d.ImagePath);

return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
AssignedDDLCHKBoxValues(blogsToUpdate);
return View(blogsToUpdate);
}

private void UpdateInstructorCourses(string[] SelectedTags, string[] SelectedCategories, Blog blogsToUpdate)
{
if (SelectedTags == null)
{
blogsToUpdate.BlogTags = new List<BlogTag>();
return;
}
if (SelectedCategories == null)
{
blogsToUpdate.BlogCategories = new List<BlogCategory>();
return;
}

var SelectedTagsHS = new HashSet<string>(SelectedTags);
var SelectedCategoriesHS = new HashSet<string>(SelectedCategories);
var blogTags = new HashSet<int>(blogsToUpdate.BlogTags.Select(c => c.BlogTagID));

foreach (var tag in _RepositoryTag.Get())
{
if (SelectedTagsHS.Contains(tag.BlogTagID.ToString()))
{
if (!blogTags.Contains(tag.BlogTagID))
{
blogsToUpdate.BlogTags.Add(tag);
}
}//if
else
{
if (blogTags.Contains(tag.BlogTagID))
{
blogsToUpdate.BlogTags.Remove(tag);
}
}//else
}//foreach tag


var blogcategories = new HashSet<int>
(blogsToUpdate.BlogCategories.Select(c => c.BlogCategoryID));
foreach (var Category in _RepositoryCategory.Get())
{
if (SelectedCategoriesHS.Contains(Category.BlogCategoryID.ToString()))
{
if (!blogcategories.Contains(Category.BlogCategoryID))
{
blogsToUpdate.BlogCategories.Add(Category);
}
}//if
else
{
if (blogcategories.Contains(Category.BlogCategoryID))
{
blogsToUpdate.BlogCategories.Remove(Category);
}
}//else
}//foreach skill
}

Answer

Your problem is that you are using multiple contexts to work with a single Entity. On your controller constructor you have these lines:

this._Repository = new GenericRepository<Blog>(new DbContext());
this._RepositoryTag = new GenericRepository<BlogTag>(new DbContext());
this._RepositoryCategory = new GenericRepository<BlogCategory>(new DbContext());

Here, you are creating 3 repositories 9that are supposed to work together) with 3 different contexts.

After that, you proceed to read from the RepositoryTag repository, here:

var tagToAdd = _RepositoryTag.GetById(int.Parse(tag));

When you do this, the object tagToAdd becomes attached to the context inside RepositoryTag. If you debug your list BlogTags where you add this tagToAdd you will see that you have a dynamic proxy, which means that that objetc is attached to a context.

After that, you use another context to fill repository category, here:

var catToAdd = _RepositoryCategory.GetById(int.Parse(cat));
blog.BlogCategories.Add(catToAdd);

Now, your blog object has references to 2 different contexts: the one you used to load the tags (RepositoryTag), and the one you used to load the blog category (RepositoryCategory).

Finally, you try to inser the blog usgin a third context:

_Repository.Insert(blog);

This will throw an exception because EF can't work with multiple contexts like this.

To solve this problem, simply instantiate a context before the repositories, and pass it to all yout repositories, like this:

this.context = new DbContext(); // The context you need to use for all operations you are performing here.
this._Repository = new GenericRepository<Blog>(this.context);
this._RepositoryTag = new GenericRepository<BlogTag>(this.context);
this._RepositoryCategory = new GenericRepository<BlogCategory>(this.context);

Now, don't forget you should dispose your contexts. This is why the most recommended and common approach is to use a code like this:

using (var ctx = new DbContext()) {

    var repo = new GenericRepository<Blog>(ctx);
    var repoTag = new GenericRepository<BlogTag>(ctx);
    var repoCategory = new GenericRepository<BlogCategory>(ctx);

    <the rest of your code where you build the `blog` object>

    ctx.SaveChanges();
}