以多对多关系保存数据

本文关键字:数据 保存 关系 | 更新日期: 2023-09-27 18:07:28

我有三个表,我正在使用User, Application, ApplicationAdministrator。ApplicationAdministrator是一个映射表,用于将User链接到具有多对多关系的Application。当我尝试保存一个新应用程序并将用户添加为管理员时,我得到以下错误:

不能定义两个对象之间的关系,因为它们附加到不同的ObjectContext对象。

所以我的下一步是创建一个BaseRepository,它有一个公共上下文可以从中提取。但是,现在当我尝试修改已经附加到上下文的实体时,我得到以下错误:

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.

为什么这是一个如此困难的过程?我已经看到了解决方案,连接,重新连接和分离,并在你的头上旋转5次,然后一切都会工作。将实体附加到一个上下文最终会导致实体的重复,这取决于我将其附加到哪个上下文。

非常感谢所有的帮助!

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

保存实体的代码段。

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

以多对多关系保存数据

如果您使用两个不同的上下文,您必须从第一个上下文分离实体,并将其附加到您想要执行更改的第二个上下文。您还必须正确配置其状态。要在DbContext API中分离实体,你需要调用:

context.Entry(entity).State = EntityState.Detached;

如果你使用相同的上下文来加载所有实体并保存它们的更改,你不需要更改它们的状态。它是通过变更跟踪自动完成的。

顺便说一句。你应该从没有资源库开始,一旦你了解了EF是如何工作的,以及资源库是如何帮助你的,你就应该开始使用资源库。

这是我最终想到的解决方案。

我创建了一个Func字典,以便在我的上下文中将实体附加到正确的EntitySet。一个缺点是您必须硬编码EntitySet名称,所以我在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);