Saving data in a many-to-many relationship
I have three tables that I am working with User, Application, ApplicationAdministrator. ApplicationAdministrator is a mapping table to link User to Application which has a many-to-many relationship. I get the following error when I try to save off a new Application with a User added as an Administrator:
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
So my next step was to create a BaseRepository that has a common context to pull from. However, now I get the following error when I try to modify an entity that is already attached to the context:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Why is this such a difficult process? I have seen the solutions to attach and reattach and detach and spin around on your head 5 times and then everything will work. Attaching the entities to one context ends up duplication one of the entities depending on which context I attach it to.
All help is greatly appreciated!
UserRepository.cs:
public class UserRepository : BaseRepository<User>, IUserRepository
{
// private ManagerDbContext _context = new ManagerDbContext();
public UserRepository(ManagerDbContext context)
: base(context) { }
public IQueryable<User> Users
{
get { return _context.Users.Include("Administrates").Include("Company"); }
}
public void SaveUser(User user)
{
_context.Entry(user).State = user.Id == 0 ? EntityState.Added : EntityState.Modified;
_context.SaveChanges();
}
public void DeleteUser(User user)
{
_context.Users.Remove(user);
_context.SaveChanges();
}
}
ApplicationRepository.cs:
public class ApplicationRepository : BaseRepository<Application>, IApplicationRepository
{
// private ManagerDbContext _context = new ManagerDbContext();
public ApplicationRepository(ManagerDbContext context)
: base(context) { }
public IQueryable<Application> Applications
{
get { return _context.Applications.Include("Administrators"); }
}
public void SaveApplication(Application app)
{
_context.Entry(app).State = app.Id == 0 ? EntityState.Added : EntityState.Modified;
_context.SaveChanges();
}
public void DeleteApplication(Application app)
{
_context.Applications.Remove(app);
_context.SaveChanges();
}
}
UserConfiguration.cs:
public UserConfiguration()
{
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(x => x.FirstName).IsRequired();
this.Property(x => x.LastName).IsRequired();
this.Property(x => x.Username).IsRequired();
this.Property(x => x.CompanyId).IsRequired();
this.HasRequired(user => user.Company).WithMany().HasForeignKey(user => user.CompanyId);
this.HasRequired(user => user.Company).WithMany(company => company.Users).WillCascadeOnDelete(false);
this.HasMany(user => user.Administrates)
.WithMany(application => application.Administrators)
.Map(map => map.MapLeftKey("UserId")
.MapRightKey("ApplicationId")
.ToTable("ApplicationAdministrators"));
}
ApplicationConfiguration.cs:
public ApplicationConfiguration()
{
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(x => x.Name).IsRequired();
this.Property(x => x.Description);
this.HasMany(application => application.Administrators)
.WithMany(user => user.Administrates)
.Map(map => map.MapLeftKey("ApplicationId")
.MapRightKey("UserId")
.ToTable("ApplicationAdministrators"));
}
Snippet for saving the entities.
long appId = Int64.Parse(form["ApplicationId"]);
long userId = Int64.Parse(form["UserId"]);
Application app = appRepository.Applications.FirstOrDefault(a => a.Id == appId);
User user = userRepository.Users.FirstOrDefault(u => u.Id == userId);
app.Administrators.Add(user);
appRepository.SaveApplication(app)开发者_如何学运维;
return RedirectToAction("Index");
If you are using two different contexts you must detach entity from the first one and attach it to the second one where you want to perform changes. You must also correctly configure its state. To detach entity in DbContext API you need to call:
context.Entry(entity).State = EntityState.Detached;
If you use the same context for loading all entities and saving their changes you don't need to change their state. It is done automatically by change tracking.
Btw. you should start without repository and start to use repository once you understand how does EF works and how could repository help you.
Here is my solution that I finally came up with.
I created a Func dictionary in order to attach an entity to the correct EntitySet in my context. The one downfall is that you have to hard code the EntitySet name some, so I did it in a static variable within my POCO.
BaseRepository.cs
public class BaseRepository<T> where T : class
{
public static ManagerDbContext baseContext;
public BaseRepository() { }
public BaseRepository(ManagerDbContext context)
{
baseContext = context;
}
private static object _entity;
public void AttachEntity(object entity)
{
_entity = entity;
entityAttachFunctions[entity.GetType().BaseType]();
}
private Dictionary<Type, Func<bool>> entityAttachFunctions = new Dictionary<Type, Func<bool>>()
{
{typeof(User), () => AttachUser()},
{typeof(Application), () => AttachApplication()}
};
private static bool AttachUser()
{
((IObjectContextAdapter)baseContext).ObjectContext.AttachTo(User.TableName, _entity);
return true;
}
private static bool AttachApplication()
{
((IObjectContextAdapter)baseContext).ObjectContext.AttachTo(Application.TableName, _entity);
return true;
}
}
UserRepository.cs
public void AttachEntity(object entity)
{
baseContext = _context;
base.AttachEntity(entity);
}
public void DetachUser(User user)
{
_context.Entry(user).State = EntityState.Detached;
_context.SaveChanges();
}
ApplicationRepository.cs
public void AttachEntity(object entity)
{
baseContext = _context;
base.AttachEntity(entity);
}
public void DetachApplication(Application app)
{
_context.Entry(app).State = EntityState.Detached;
_context.SaveChanges();
}
AdminController.cs
long appId = Int64.Parse(form["ApplicationId"]);
long userId = Int64.Parse(form["UserId"]);
Application app = appRepository.Applications.FirstOrDefault(a => a.Id == appId);
User user = userRepository.Users.FirstOrDefault(u => u.Id == userId);
userRepository.DetachUser(user);
appRepository.AttachEntity(user);
app.Administrators.Add(user);
appRepository.SaveApplication(app);
精彩评论