EF CORE 一对多、多对多添加、修改

创建.net core 控制台应用程序:

项目文件结构:

添加如下NuGet包:

Microsoft.EntityFrameworkCore

Microsoft.EntityFrameworkCore.SqlServer

Microsoft.EntityFrameworkCore.Tools

添加如下实体类:

/// <summary>
/// 部门
/// </summary>
public class Dept
{
    private Dept() { }
    /// <summary>
    /// 单独添加部门
    /// </summary>
    /// <param name="name"></param>
    /// <param name="parentId"></param>
    public Dept(string name,long? parentId)
    {
        Name = name;
        ParentId = parentId;
    }
    /// <summary>
    /// 添加部门并添加用户
    /// </summary>
    /// <param name="name"></param>
    /// <param name="parentId"></param>
    /// <param name="UserDepts"></param>
    public Dept(string name, long? parentId, ICollection<UserDept> userDepts)
    {
        Name = name;
        ParentId = parentId;
        UserDepts = userDepts;
    }
    public long Id { get; private set; }
    public string Name { get; private set; }
    public long? ParentId { get; private set; }
    public virtual Dept Parent { get; private set; }
    public virtual ICollection<Dept> Depts { get; private set; }
    public virtual ICollection<UserDept> UserDepts { get; private set; }
}

/// <summary>
/// 用户
/// </summary>
public class User
{
    private User() { }
    public User(string name, string phone, int sex)
    {
        Name = name;
        Phone = phone;
        Sex = sex;
    }

    public User(long id,string name, string phone, int sex)
    {
        Name = name;
        Phone = phone;
        Sex = sex;
        Id = id;
    }

    public User(string name, string phone, int sex, ICollection<UserDept> userDepts, ICollection<UserCompany> userCompanys)
    {
        Name = name;
        Phone = phone;
        Sex = sex;
        UserDepts = userDepts;
        UserCompanys = userCompanys;
    }
    
    public User(long id,string name, string phone, int sex, ICollection<UserDept> userDepts, ICollection<UserCompany> userCompanys)
    {
        Name = name;
        Phone = phone;
        Sex = sex;
        UserDepts = userDepts;
        UserCompanys = userCompanys;
        Id = id;
    }
    public long Id { get; private set; }
    public string Name { get; private set; }
    public string Phone { get; private set; }
    public int Sex { get; private set; }
    public virtual ICollection<UserDept> UserDepts { get; private set; }
    public virtual ICollection<UserCompany> UserCompanys { get; private set; }
}

/// <summary>
/// 用户公司
/// </summary>
public class UserCompany
{
    private UserCompany() { }
    /// <summary>
    /// 通过用户添加公司
    /// </summary>
    /// <param name="name"></param>
    public UserCompany(string name)
    {
        Name = name;
    }
    /// <summary>
    /// 单独为用户添加公司
    /// </summary>
    /// <param name="name"></param>
    /// <param name="userId"></param>
    public UserCompany(string name, long userId)
    {
        Name = name;
        UserId = userId;
    }
    /// <summary>
    /// 通过公司添加用户
    /// </summary>
    /// <param name="name"></param>
    /// <param name="user"></param>
    public UserCompany(string name, User user)
    {
        Name = name;
        User = user;
    }
    public long Id { get; private set; }
    public string Name { get; private set; }
    public long UserId { get; private set; }
    public virtual User User { get; private set; }
}

/// <summary>
/// 用户部门
/// </summary>
public class UserDept
{
    private UserDept() { }
    public UserDept(long? userId, long? deptId)
    {
        if (userId.HasValue)
            UserId = userId.Value;

        if (deptId.HasValue)
            DeptId = deptId.Value;
    }
    public long Id { get; private set; }
    public long UserId { get; private set; }
    public virtual User User { get; private set; }
    public long DeptId { get; private set; }
    public virtual Dept Dept { get; private set; }
}

 添加DbContext:

public class TestContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Data Source=DESKTOP-2H9332M;Initial Catalog=Test;Integrated Security=True");
    }

    public DbSet<User> Users { get; set; }
    public DbSet<Dept> Depts { get; set; }
    public DbSet<UserCompany> UserCompanys { get; set; }
    public DbSet<UserDept> UserDepts { get; set; }
}

在程序包控件管理台分别执行如下命令:

migrations add init

update-database

执行成功后生成的数据库关系图如下:

在Program类中添加如下测试代码:

class Program
{
    static void Main(string[] args)
    {
        var context = new TestContext();

        //添加部门
        context.Set<Dept>().Add(new Dept("部门1", null));
        //添加用户
        context.Set<User>().Add(new User("用户1", "1111111", 0));
        //添加公司
        context.Set<UserCompany>().Add(new UserCompany("公司1", 1));

        //添加部门、用户部门
        context.Set<Dept>().Add(new Dept(
                 "部门2",
                  1,
                  new List<UserDept>()
                  {
                     new UserDept(1,null)
                  }));

        //添加用户、用户部门、用户公司
        context.Set<User>().Add(new User(
            "张三",
            "13037318682",
            1,
            new List<UserDept>
            {
                new UserDept(null, 1)
            },
            new List<UserCompany>
            {
                new UserCompany("ABC公司")
            }));

        //添加用户、公司
        context.Set<UserCompany>().Add(new UserCompany("公司2", new User("用户2", "222222", 2)));

        context.SaveChanges();
    }
}

运行代码添加的数据如下:

接着我们来测试下一对多的更新代码如下:

//更新用户、用户部门、用户公司
context.Set<User>().Update(new User(1, "李四", "13037318682", 1,
    new List<UserDept> {
        new UserDept(null,2)
    },
    new List<UserCompany>
    {
        new UserCompany("ABC公司修改")
    }));
context.SaveChanges();

 结果如下:

使用这种方式修改会在UserDept、UserCompany表中新增一条数据,这种结果也正是我们业务所需要的。

如果我想修改一对多中的子表某一条记录而不是增加一条数据又该如何操作呢?请看如下代码:

context.Set<User>().Update(new User(2, "用户2修改2", "13037318682", 1,
    new List<UserDept> {
        new UserDept(2,2,2)
    },
    new List<UserCompany>
    {
        new UserCompany(1,"ABCD公司修改")
    }));

context.SaveChanges();

结果如下:

由此可以看出在使用修改操作时需要指定主键Id。 

<think>好的,我现在需要帮助用户理解为什么在使用Entity Framework Core的modelBuilder配置时,他们的代码生成了一个唯一约束(unique),以及如何将原本的一对一关系改为一对关系,避免生成这个唯一约束。用户提供的代码片段中,配置了一对一关系,导致生成了唯一索引,而他们可能希望改为一对,这样就不会有唯一约束了。 首先,我需要回顾EF Core中关系配置的基础知识。在一对一关系中,EF Core通常会在外键字段上创建唯一约束,以确保每个主实体只能对应一个从实体,反之亦然。而在一对关系中,外键字段不需要唯一约束,因为个从实体可以指向同一个主实体。 用户提供的代码示例中使用了`HasOne().WithOne()`,这明确告诉EF Core建立一对一关系,因此会自动生成唯一约束。如果他们希望改为一对,就需要将配置改为`HasOne().WithMany()`,这样EF Core就不会添加唯一约束了。 接下来,我需要分析用户提供的引用内容。引用[1]中的DDL SQL显示,在一对一配置下,生成了一个唯一索引`IX_SingleModel_TargetId`,而在一对的情况下,应该没有这个唯一索引。引用[3]提到了配置错误时可能出现的异常,提示无法确定依赖方,这可能是因为没有正确配置外键或者关系类型。 用户可能的问题在于,他们的当前配置是一对一,但实际业务需求可能是一对,因此需要调整配置方法。需要解释如何修改代码,将`WithOne()`改为`WithMany()`,并确保外键正确配置,这样生成的数据库结构就不会包含唯一约束。 另外,需要说明为什么EF Core一对一关系中强制唯一约束,而一对不需要。这是因为在一对中,个子实体可以引用同一个父实体,外键值可以重复;而一对一要求外键唯一,以确保每个子实体唯一对应一个父实体。 可能还需要考虑用户是否在配置中错误地指定了导航属性,例如在两端都使用了单一导航属性,导致EF Core误认为是一对一关系。这时需要检查实体类的导航属性定义,确保一对关系的两端分别是单个引用和集合。 最后,总结修改步骤:将`WithOne()`改为`WithMany()`,调整导航属性为集合类型,并重新生成迁移,观察生成的SQL是否去除了唯一约束。同时,验证业务逻辑是否允许一对关系,确保数据完整性。</think>### EF Core 配置一对关系避免生成唯一约束的分析 根据你的配置代码和需求,EF Core生成唯一约束的核心原因在于**当前配置是一对一关系**,而若要改为一对关系并消除唯一约束,需要从**关系配置方法**和**导航属性定义**两个层面调整。以下是具体分析: --- #### 一、当前配置生成唯一约束的原因 原始代码片段: ```csharp modelBuilder.Entity<InWareHouseCargo>(x => { x.ToTable("InWareHouseCargo"); x.HasKey(x => x.Id); x.HasOne(x => x.CargoBase) .WithOne(s => s.inWareHouseCargo) .HasForeignKey<Cargo>(x => x.CargoNo) .HasPrincipalKey<InWareHouseCargo>(m => m.CargoNo); }); ``` **问题根源**: 1. 使用了`.WithOne()`,强制声明**一对一关系** 2. 一对一关系要求外键`CargoNo`必须唯一,否则无法保证实体间的唯一映射[^1] 3. EF Core自动生成唯一约束以保证数据完整性(如引用[1]中的`IX_SingleModel_TargetId`索引) --- #### 二、配置一对关系的核心方法 若需改为**一对关系**(即一个`InWareHouseCargo`对应个`Cargo`),需调整以下配置: ##### 1. **导航属性定义** ```csharp // InWareHouseCargo 实体类中定义集合导航 public class InWareHouseCargo { public ICollection<Cargo> Cargos { get; set; } // 一对的集合导航 } // Cargo 实体类中定义反向导航 public class Cargo { public InWareHouseCargo InWareHouseCargo { get; set; } // 反向引用 } ``` ##### 2. **Fluent API 配置** ```csharp modelBuilder.Entity<InWareHouseCargo>(x => { x.ToTable("InWareHouseCargo"); x.HasKey(x => x.Id); // 关键修改:将 WithOne() 改为 WithMany() x.HasMany(x => x.Cargos) // 一对关系起点 .WithOne(c => c.InWareHouseCargo) // 反向导航 .HasForeignKey(c => c.CargoNo) // 外键字段 .HasPrincipalKey(m => m.CargoNo); // 主实体关联字段(可选) }); ``` ##### 3. **生成的差异** | 配置类型 | 生成SQL约束 | 数据关系示例 | |------------|--------------------------|---------------------------| | 一对一关系 | 外键列添加`UNIQUE`约束 | `InWareHouseCargo` ↔ `Cargo`(1:1) | | 一对关系 | **无**`UNIQUE`约束 | `InWareHouseCargo` → `Cargo`(1:N) | --- #### 三、关键配置对比 | 配置项 | 一对一关系 | 一对关系 | |----------------------|-----------------------------------|-----------------------------------| | **导航属性** | 双方均为单对象引用 | 主实体用集合,从实体用单对象引用 | | **Fluent API** | `.HasOne().WithOne()` | `.HasMany().WithOne()` | | **外键约束** | 自动生成`UNIQUE`索引 | 仅普通外键,无唯一约束 | | **典型应用场景** | 主从实体严格一一对应(如身份证) | 主实体拥有个子实体(如订单-商品) | --- #### 四、验证是否成功避免唯一约束 通过EF Core的迁移命令生成SQL脚本验证: ```bash dotnet ef migrations script ``` 检查生成的SQL中**是否包含`UNIQUE`索引**: ```sql -- 一对一关系会生成 CREATE UNIQUE INDEX [IX_Cargo_CargoNo] ON [Cargo] ([CargoNo]); -- 一对关系不会生成UNIQUE索引 ALTER TABLE [Cargo] ADD CONSTRAINT [FK_Cargo_InWareHouseCargo_CargoNo] FOREIGN KEY ([CargoNo]) REFERENCES [InWareHouseCargo] ([CargoNo]); ``` --- #### 五、常见错误排查 1. **导航属性不匹配** 若从实体未定义集合导航或主实体未定义单导航,EF Core可能仍推断为其他关系类型[^3]。 2. **未正确指定外键** 需通过`.HasForeignKey()`明确指定外键字段,否则EF Core可能自动生成隐藏字段。 3. **主键配置冲突** 若使用`.HasPrincipalKey()`指定非主键字段作为关联目标,需确保该字段在数据库中具有唯一性(但仅对一对一关系有效)。 --- ### 相关问题 1. 如何判断EF Core当前配置的是哪种关系类型? 2. 一对关系中是否必须使用集合导航属性? 3. 如何手动禁用EF Core的自动约束生成?
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值