ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

EF Core 必须有主键

EF Core 必须有主键

EF Core 无主键表操作指南

重要结论:EF Core 必须有主键

使用 EF Core 的前提是实体必须有主键,这是 EF Core 核心机制决定的,不是可选项。

为什么必须有主键?

EF Core 功能 主键的作用
删除 (Delete) 用主键唯一标识要删除的记录
更新 (Update) 用主键定位要更新的行
身份映射 (Identity Map) 用主键去重,确保内存中只有一个实体实例
变更跟踪 (ChangeTracker) 用主键跟踪实体的状态变化
关系导航 外键关联需要主键作为目标

无主键表的删除对比

-- 有主键的表:精准删除 1 条
DELETE FROM Users WHERE Id = 1;-- 无主键表:EF Core 无法生成正确的 DELETE 语句
-- 可能:删除所有匹配行,或抛出异常
DELETE FROM ifi_file
WHERE ProgramId = 'A' AND EmailAddress = 'x@a.com';
-- ⚠️ 如果有多行匹配,会全部删除!

结论

┌─────────────────────────────────────────────────────────┐
│  使用 EF Core → 必须配置主键 → 方案一/二/五/六          │
│  跳过 EF Core → 直接用 SQL → 方案四                     │
└─────────────────────────────────────────────────────────┘

问题背景

现有数据库表 ifi_file 没有主键,使用 EF Core 操作时可能会遇到:

  • 删除操作无法正确识别记录
  • 更新操作可能影响多行
  • 并发问题

解决方案

方案一:添加 Id 主键列(推荐)

适用场景:可以修改数据库结构

  1. 数据库添加主键列:

    ALTER TABLE ifi_file ADD COLUMN id INTEGER PRIMARY KEY AUTOINCREMENT;
    
  2. 修改 BaseEntity:

    namespace Longforce.EmailSender.Models;public abstract class BaseEntity
    {public long Id { get; set; }
    }
    
  3. 修改 MailEntity:

    public class MailEntity : BaseEntity
    {// 其他属性保持不变
    }
    
  4. 配置主键(AppDbContext.cs):

    private static void ConfigureMailer(EntityTypeBuilder<MailEntity> entity)
    {entity.ToTable("ifi_file");entity.HasKey(e => e.Id);  // 添加主键配置// ... 其他属性映射
    }
    

方案二:使用复合主键(无需修改表结构)

适用场景:无法修改数据库结构,但有唯一标识组合

  1. 确定业务唯一键组合:

    • ifi01 (Customer) + ifi02 (ProgramId) + ifi05 (EmailAddress) 可能是唯一组合
  2. 配置复合主键(AppDbContext.cs):

    private static void ConfigureMailer(EntityTypeBuilder<MailEntity> entity)
    {entity.ToTable("ifi_file");// 使用复合主键entity.HasKey(e => new { e.ProgramId, e.Customer, e.EmailAddress });// ... 其他属性映射
    }
    
  3. 删除操作注意事项:

    // 需要完整匹配所有主键字段
    var entitiesToRemove = existingEntities.Where(m => normalEmailsToRemove.Contains(m.EmailAddress, StringComparer.OrdinalIgnoreCase)).ToList();
    

方案三:使用 ROWID(SQLite 特有,不适用)

⚠️ 此方案仅适用于 SQLite,SQL Server 不支持 __rowid__。


方案四:使用原始 SQL 执行删除(SQL Server 推荐)

适用场景:表无主键且无法修改,直接使用 SQL

修改 MailService.cs 注入 AppDbContext:

public class MailService : IMailService
{private readonly IMailRepository _mailRepository;private readonly IUnitOfWork _unitOfWork;private readonly AppDbContext _context;public MailService(IMailRepository mailRepository, IUnitOfWork unitOfWork, AppDbContext context){_mailRepository = mailRepository;_unitOfWork = unitOfWork;_context = context;}// 删除普通收件人private void RemoveNormalReceivers(string programId, List<string> emailsToRemove){foreach (var email in emailsToRemove){var sql = @"DELETE FROM ifi_file WHERE ifi02 = @programId AND ifi05 = @email AND (ifi06 IS NULL OR ifi06 != 'CCReceiver')";_context.ExecuteNonQuery(sql,new SqlParameter("@programId", programId),new SqlParameter("@email", email));}}// 删除抄送收件人private void RemoveCCReceivers(string programId, List<string> emailsToRemove){foreach (var email in emailsToRemove){var sql = @"DELETE FROM ifi_file WHERE ifi02 = @programId AND ifi05 = @email AND ifi06 = 'CCReceiver'";_context.ExecuteNonQuery(sql,new SqlParameter("@programId", programId),new SqlParameter("@email", email));}}
}

在 UpdateMail 中使用:

public void UpdateMail(MailDTO mailDTO)
{// ... 现有逻辑// 使用 SQL 直接删除(更可靠)if (normalEmailsToRemove.Count > 0){RemoveNormalReceivers(mailDTO.ProgramId, normalEmailsToRemove);}if (ccEmailsToRemove.Count > 0){RemoveCCReceivers(mailDTO.ProgramId, ccEmailsToRemove);}// 添加新记录逻辑保持不变// ...
}

方案五:添加唯一标识列(Guid)

适用场景:需要分布式唯一标识

  1. 数据库添加列:

    ALTER TABLE ifi_file ADD COLUMN id UNIQUEIDENTIFIER DEFAULT NEWID();
    
  2. 修改 BaseEntity:

    public abstract class BaseEntity
    {public Guid Id { get; set; }
    }
    
  3. SQL Server 中创建索引(提高查询效率):

    CREATE NONCLUSTERED INDEX IX_ifi_file_id ON ifi_file(id);
    

方案六:使用数据库触发器自动填充主键

适用场景:无法修改应用代码,但可以添加触发器

  1. 添加主键列:

    ALTER TABLE ifi_file ADD COLUMN id BIGINT IDENTITY(1,1) PRIMARY KEY;
    
  2. 创建触发器(确保历史数据有 id):

    -- 为历史数据填充 id
    UPDATE ifi_file SET id = NEXT VALUE FOR dbo.IdSequence WHERE id IS NULL;
    

推荐方案对比

方案 优点 缺点 SQL Server 推荐场景
方案一 (添加Id) 简单、可靠 需要修改表结构 ✅ 新项目、可修改表结构
方案二 (复合主键) 无需修改表结构 可能存在重复数据 ✅ 业务上有唯一键组合
方案四 (SQL) 最可靠、无主键依赖 绕过 EF Core ✅ 紧急修复、无主键表
方案五 (Guid) 分布式友好 占用空间大 ✅ 分布式系统
方案六 (触发器) 可处理历史数据 增加维护成本 ✅ 已有大量历史数据

当前代码修复建议

如果选择方案四(SQL),修改 MailService.cs 中的删除逻辑:

// 替换 RemoveRange 为 SQL 删除
if (entitiesToRemove.Count > 0)
{foreach (var entity in entitiesToRemove){var sql = "DELETE FROM ifi_file WHERE ifi02 = @programId AND ifi05 = @email AND (ifi06 IS NULL OR ifi06 != 'CCReceiver')";// 需要注入 DbContext 或使用 AppDbContext 的 ExecuteNonQuery}
}if (ccEntitiesToRemove.Count > 0)
{foreach (var entity in ccEntitiesToRemove){var sql = "DELETE FROM ifi_file WHERE ifi02 = @programId AND ifi05 = @email AND ifi06 = 'CCReceiver'";}
}
返回列表