多对多实体框架和存储库模式插入/更新

Posted

技术标签:

【中文标题】多对多实体框架和存储库模式插入/更新【英文标题】:Many-to-many Entity framework and repository pattern Insert/Update 【发布时间】:2012-11-27 12:46:22 【问题描述】:

我正在使用 EF 代码优先方法和流利的 api。我的申请中有一个注册表单,在其中注册候选人可以从下拉列表中选择多个选项(对注册表单上的下拉列表感兴趣),其中包含一组预定义的选项(将来可能会增加,但机会非常少)。当用户提交表单时,我想将此记录保存到数据库中。所以我创建了以下实体。

将保存注册候选人信息的参与者类

public class Participant
    
        public Participant()
        
            Interests = new Collection<Interest>();
        
        [Key, ForeignKey("User")]
        public int Id  get; set; 
        [DisplayName("First Name")]
        [StringLength(50, ErrorMessage = "First name cannot be more than 50 characters")]
        [Required(ErrorMessage = "You must fill in first name")]
        public string FirstName  get; set; 

        [DisplayName("Last Name")]
        [StringLength(50, ErrorMessage = "Last name cannot be more than 50 characters")]
        [Required(ErrorMessage = "You must fill in last name")]
        public string LastName  get; set; 

        [Required(ErrorMessage = "You must indicate your full birthday")]
        [DisplayName("Birthday")]
        [DataType(DataType.DateTime)]
        public DateTime BirthDate  get; set; 

        [DisplayName("Gender")]
        [Required(ErrorMessage = "You must select gender")]
        public int Gender  get; set; 

        public string Address  get; set; 

        public int CountryId  get; set; 
        public Country Country  get; set; 

        [DisplayName("Zip code")]
        [StringLength(10, ErrorMessage = "Zip code cannot be more than 10 characters")]
        public string ZipCode  get; set; 

        public string Mobile  get; set; 

        public string PhotoUrl  get; set; 

        public virtual User User  get; set; 

        public virtual ICollection<Interest> Interests  get; set; 

        public string MedicalConditions  get; set; 

注册表单上的“感兴趣”下拉列表将填充的兴趣类用户可以从“感兴趣”下拉列表中选择多个选项

兴趣班

 public class Interest
    
        public Interest()
        
            Participants = new Collection<Participant>();
        
        public int Id  get; set; 
        public string InterestName  get; set; 
        public virtual ICollection<Participant> Participants  get; private set; 
    

为了保持每个参与者的兴趣,我在 DB 中创建了一个具有以下架构的 ParticipantInterests 表。 ParticipantInterests Id (PK) ParticipantId (FK from Participants table) InterestId (FK Interests table)

我添加了 public virtual ICollection Participants get;放; in Interest 模型和

public virtual ICollection Interests get;放; 在 Participant 模型中形成多对多关联。 我的数据上下文类如下

public class STNDataContext : DbContext
    
        public DbSet<Participant> Participants  get; set; 
        public DbSet<User> Users  get; set; 
        public DbSet<Country> Countries  get; set; 
        public DbSet<Interest> Interests  get; set; 
        public DbSet<Role> Roles  get; set; 
        public DbSet<SecurityQuestion> SecurityQuestions  get; set; 

        public DbSet<Tour> Tours  get; set; 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        
            modelBuilder.Entity<Participant>()
                .HasKey(p => p.Id);

            modelBuilder.Entity<User>()
                .HasOptional(u => u.Participant)
                .WithRequired();

            modelBuilder.Entity<Participant>()
            .HasMany(p => p.Interests)
            .WithMany(i => i.Participants)
            .Map(m =>
            
                m.ToTable("ParticipantInterests");
                m.MapLeftKey("ParticipantId");
                m.MapRightKey("InterestId");
            );

            modelBuilder.Entity<User>().HasRequired(u => u.Role);
            modelBuilder.Entity<Participant>().HasRequired(p => p.Country);
            modelBuilder.Entity<User>().HasOptional(u => u.SecurityQuestion);
        


        public virtual void Commit()
        
            base.SaveChanges();
        

控制器操作代码

public virtual ActionResult Register(StudentRegisterViewModel studentRegisterViewModel)
        
            if (ModelState.IsValid)
            
                if (_userService.IsUserExists(studentRegisterViewModel.Participant.User) == false)
                
                    studentRegisterViewModel.Participant.User.Username = studentRegisterViewModel.Username;
                    studentRegisterViewModel.Participant.User.Email = studentRegisterViewModel.Email;
                    studentRegisterViewModel.Participant.User.DateCreated = DateTime.Now;
                    studentRegisterViewModel.Participant.User.Id = 3;
                    studentRegisterViewModel.Participant.User.IsApproved = false;
                    studentRegisterViewModel.Participant.User.RoleId = 2;
                    studentRegisterViewModel.Participant.CountryId = 1;
                    var interests = new List<Interest>();
                    foreach (var interestItem in studentRegisterViewModel.SelectedInterests)
                    
                        var interest = new Interest  Id = interestItem ;
                        interest.Participants.Add(studentRegisterViewModel.Participant);
                        interests.Add(interest);
                        studentRegisterViewModel.Participant.Interests.Add(interest);
                    
                    studentRegisterViewModel.Participant.Interests = interests;
                    _participantService.CreatParticipant(studentRegisterViewModel.Participant);
                    var user = _userService.GetUser(studentRegisterViewModel.Participant.User.Username);
                
            
            studentRegisterViewModel.Gender =
                Enum.GetNames(typeof(Gender)).Select(
                    x => new KeyValuePair<string, string>(x, x.ToString(CultureInfo.InvariantCulture)));
            studentRegisterViewModel.Interests = _interestService.GetAllInterests();
            return View(studentRegisterViewModel);
        

参与者存储库 (DAL)

 public class ParticipantRepository : Repository<Participant>, IParticipantRepository 
    
        public ParticipantRepository(IDatabaseFactory databaseFactory)
            : base(databaseFactory)
        
        
    

参与者服务 (BLL)

public class ParticipantService : IParticipantService
    
        private readonly IParticipantRepository _participantRepository;
        private readonly IUnitOfWork _unitOfWork;

        public ParticipantService(IParticipantRepository participantRepository, IUnitOfWork unitOfWork)
        
            this._participantRepository = participantRepository;
            this._unitOfWork = unitOfWork;
        

        public void CreatParticipant(Participant participant)
        
            _participantRepository.Add(participant);
            _unitOfWork.Commit();
        

数据库工厂

public class DatabaseFactory : Disposable, IDatabaseFactory
    
        private STNDataContext _stnDataContext;
        public DatabaseFactory()
        
            Database.SetInitializer<STNDataContext>(null);
        
        public STNDataContext Get()
        
            return _stnDataContext ?? (_stnDataContext = new STNDataContext());
        
        protected override void DisposeCore()
        
            if (_stnDataContext != null)
                _stnDataContext.Dispose();
        
    

工作单元类

public class UniOfWork : IUnitOfWork

    private readonly IDatabaseFactory _databaseFactory;
    private STNDataContext _stnDataContext;

    public UniOfWork(IDatabaseFactory databaseFactory)
    
        this._databaseFactory = databaseFactory;
    

    public STNDataContext StnDataContext
    
        get  return _stnDataContext ?? (_stnDataContext = _databaseFactory.Get()); 
    

    public void Commit()
    
        StnDataContext.Commit();
    


当我尝试创建参与者时,我收到以下错误。

无法将值 NULL 插入到列“InterestName”、表“StudyTourNetworkDB.dbo.Interests”中;列不允许空值。 INSERT 失败。\r\n语句已终止。

理想情况下,根据我的想法,它应该在 Participants 表中插入 Participant Information 并在 ParticipantsInterests 表中插入 Participants Interests。但它也试图在兴趣表中插入记录,这也不应该发生。请帮我解决这个问题。创建多对多关联可能是我做错了。

谢谢

注意:我可以理解这个问题,因为兴趣集合没有被添加/附加到上下文,但我不知道如何将兴趣集合添加到具有存储库模式和工作单元的相同上下文。

请提供解决方案。提前致谢

【问题讨论】:

请将您的代码减少到最低限度。您的问题被淹没在文字墙中。 【参考方案1】:

您是正确的,因为您的兴趣对象正在重新添加,因为您的模型中保存的副本没有被 EF 跟踪,因此它认为它们是新的。相反,您需要从存储库中查找版本,然后添加这些版本。

代替:

var interests = new List<Interest>();
foreach (var interestItem in studentRegisterViewModel.SelectedInterests)

    var interest = new Interest  Id = interestItem ;
    interest.Participants.Add(studentRegisterViewModel.Participant);
    interests.Add(interest);
    studentRegisterViewModel.Participant.Interests.Add(interest);

studentRegisterViewModel.Participant.Interests = interests;

尝试类似:

// Look up the actual EF entities which match your selected items.  You'll  
// probably need to adapt this to make it work
var selectedInterestIds = studentRegisterViewModel.SelectedInterests.Select(i => i.Id);
var interests = _interestService.GetAllInterests().Where(i => selectedInterestIds.Contains(i.Id));    
studentRegisterViewModel.Participant.Interests = interests;

请注意,对于多对多关系,您不需要设置双方 - 在您的示例中,您填写了兴趣实体的参与者字段 - 这将由 EF 自动设置,因为您是将其添加到 Participant 的 Interests 属性中。

【讨论】:

嘿,感谢您的快速回答。 现在我收到此错误。一个实体对象不能被多个 IEntityChangeTracker 实例引用 嗨,理查德,感谢您的回答。您的解决方案有效,但现在给我这个错误 IEntityChangeTracker 的多个实例无法引用实体对象。对此的任何解决方案。再次感谢 听起来您的存储库正在使用单独的 dbcontexts - 工作单元应该在它们之间共享,以便它们的实体来自相同的上下文。 抱歉,Richards 仍然无法找出解决此问题的方法。你能帮忙吗?

以上是关于多对多实体框架和存储库模式插入/更新的主要内容,如果未能解决你的问题,请参考以下文章

实体框架使用 Codefirst、通用存储库、工作单元模式保存多对多关系

实体框架:多对多插入重复

插入/更新多对多实体框架。我该怎么做?

首先在实体框架代码中与中间对象进行多对多映射

在不使用实体框架的多对多中插入记录

使用 SQL 和 Linq 的多对多关系(实体框架/实体)