以多对多关系保存数据

时间:2011-09-01 11:49:22

标签: c# .net entity-framework-4.1

我有三个表,我正在使用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");

2 个答案:

答案 0 :(得分:1)

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

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

如果使用相同的上下文加载所有实体并保存其更改,则无需更改其状态。它由变更跟踪自动完成。

顺便说一下。你应该在没有存储库的情况下开始,一旦了解了EF的工作原理以及存储库如何帮助你,就开始使用存储库。

答案 1 :(得分:0)

这是我最终提出的解决方案。

我创建了一个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);