.NET Core 5中未声明ChatUser表的来源排查及SaveChanges异常问题
我在.NET Core 5应用中实现聊天功能,创建了Chats、Messages和UsersChats三张表,对应Chat、Message、UserChat三个模型。但迁移后发现多生成了一张ChatUser表,包含ChatsId和UsersId列,且已有数据填充,而UserChat表数据为空。删除ChatUser表后,执行this.data.SaveChanges();时抛出异常,因为代码尝试向该表写入数据。我既没创建ChatUser模型也没定义对应的DbSet,完全不清楚它的来源。
相关代码:
Chat模型
public class Chat { public int Id { get; set; } public string Name { get; set; } public ICollection<Message> Messages { get; set; } = new List<Message>(); public ICollection<User> Users { get; set; } = new List<User>(); public IEnumerable<UserChat> UserChats { get; set; } }
Message模型
public class Message { public int Id { get; set; } public int ChatId { get; set; } [JsonIgnore] public Chat Chat { get; set; } public string UserId { get; set; } public User User { get; set; } public string Content { get; set; } public DateTime CreatedAt { get; set; } }
UserChat模型
public class UserChat { public string UserId { get; set; } public User User { get; set; } public int ChatId { get; set; } public Chat Chat { get; set; } }
User模型
public class User : IdentityUser { [JsonIgnore] public ICollection<Chat> Chats { get; set; } = new List<Chat>(); public IEnumerable<UserChat> UserChats { get; set; } }
DbSet定义
public DbSet<Chat> Chats { get; set; } public DbSet<Message> Messages { get; set; } public DbSet<UserChat> UsersChats { get; set; }
关系配置
builder .Entity<Message>() .HasOne(m => m.Chat) .WithMany(c => c.Messages) .HasForeignKey(m => m.ChatId) .OnDelete(DeleteBehavior.Restrict); builder .Entity<UserChat>() .HasKey(uc => new { uc.UserId, uc.ChatId });
报错方法
public void AddUserToChat(string userId, string chatName) { var chatId = this.data .Chats .Where(c => c.Name == chatName) .Select(c => c.Id) .FirstOrDefault(); var user = this.data .Users .Where(c => c.Id == userId) .FirstOrDefault(); var chat = this.data .Chats .Where(c => c.Name == chatName) .FirstOrDefault(); chat.Users.Add(user); this.data.SaveChanges(); }
迁移文件中ChatUser表的生成代码
migrationBuilder.CreateTable( name: "ChatUser", columns: table => new { ChatsId = table.Column<int>(type: "int", nullable: false), UsersId = table.Column<string>(type: "nvarchar(450)", nullable: false) }, constraints: table => { table.PrimaryKey("PK_ChatUser", x => new { x.ChatsId, x.UsersId }); table.ForeignKey( name: "FK_ChatUser_AspNetUsers_UsersId", column: x => x.UsersId, principalTable: "AspNetUsers", principalColumn: "Id", onDelete: ReferentialAction.Cascade); table.ForeignKey( name: "FK_ChatUser_Chats_ChatsId", column: x => x.ChatsId, principalTable: "Chats", principalColumn: "Id", onDelete: ReferentialAction.Cascade); });
1. 问题根源
EF Core会自动识别模型中的双向多对多关系:Chat中的ICollection<User> Users和User中的ICollection<Chat> Chats,默认生成名为ChatUser的连接表。而你手动定义了UserChat作为连接表,导致EF同时处理了两种多对多配置,生成了两张连接表。
2. 修复步骤
(1)移除冗余的导航属性
删除Chat中的ICollection<User> Users和User中的ICollection<Chat> Chats,因为你已经通过UserChat手动管理多对多关系:
修改后的Chat模型:
public class Chat { public int Id { get; set; } public string Name { get; set; } public ICollection<Message> Messages { get; set; } = new List<Message>(); public ICollection<UserChat> UserChats { get; set; } = new List<UserChat>(); }
修改后的User模型:
public class User : IdentityUser { [JsonIgnore] public ICollection<UserChat> UserChats { get; set; } = new List<UserChat>(); }
(2)完善UserChat的关系配置
在OnModelCreating中明确配置UserChat与User、Chat的关系:
builder.Entity<UserChat>() .HasKey(uc => new { uc.UserId, uc.ChatId }); builder.Entity<UserChat>() .HasOne(uc => uc.User) .WithMany(u => u.UserChats) .HasForeignKey(uc => uc.UserId) .OnDelete(DeleteBehavior.Restrict); builder.Entity<UserChat>() .HasOne(uc => uc.Chat) .WithMany(c => c.UserChats) .HasForeignKey(uc => uc.ChatId) .OnDelete(DeleteBehavior.Restrict);
(3)修复AddUserToChat方法
现在需要通过UserChat添加用户与聊天的关联,而不是直接操作chat.Users:
public void AddUserToChat(string userId, string chatName) { var chat = this.data.Chats.FirstOrDefault(c => c.Name == chatName); if (chat == null) return; var existingUserChat = this.data.UsersChats .FirstOrDefault(uc => uc.UserId == userId && uc.ChatId == chat.Id); if (existingUserChat != null) return; var userChat = new UserChat { UserId = userId, ChatId = chat.Id }; this.data.UsersChats.Add(userChat); this.data.SaveChanges(); }
(4)清理迁移并重新生成
- 删除之前包含
ChatUser表的迁移文件 - 执行
Add-Migration生成新的迁移 - 执行
Update-Database更新数据库,此时只会生成你需要的UserChat表
3. 验证
完成上述步骤后,数据库中不再有ChatUser表,所有用户与聊天的关联数据都会存储在UserChat表中,执行AddUserToChat方法也能正常保存数据。
内容的提问来源于stack exchange,提问作者Alexander Nedelchev

