当前位置: 首页 > news >正文

EFcore不使用外键,处理一对多关系

主键

命名约定

如果实体类属性命名为Id的或者为(实体名+Id)的,自动为主键

如果不符合命名约定使用HasKey(e => e.主键属性).HasName("PrimaryKey_属性名")//默认pk_属性名

默认外键

public class Post { public int PostId { get; set; } public string Title { get; set; } = null!; public string Content { get; set; } = null!; public int BlogId { get; set; } public Blog? BlogEntity { get; set; } }
public class Blog { public int BlogId { get; set; } public string Url { get; set; } = null!; public DateTime CreatedTime { get; set; } /// <summary> /// 最后更新时间 /// </summary> public DateTime UpdatedTime { get; set; } public List<Post> Posts { get; } = new(); }

必须要有导航属性

Post.Blog是引用导航属性

Blog.Posts是集合导航属性

只要有其一,才会把Post.BlogId当做外键生成,2个都没不行。

如果依赖实体包含一个名称与以下模式之一匹配的属性,则该属性将被配置为外键

主实体(即被依赖的实体,通常是关系里 “一” 的那一方对应的实体)的主键属性的名称。

外键命名约定,当主题属性里有以下其中之一的属性命名,则当成外键

当有引用导航名

1导航属性名+主键属性名 BlogEntity+BlogId(默认)

2导航属性名+Id BlogEntity+Id

当不存在引用导航属性,只有集合导航属性

3主实体名+主键属性名 Blog+BlogId(默认)

4主实体名+Id Blog+Id

注:如果主实体的主键属性名是 BlogId(而非 Id),则生成的外键列名仍为 BlogId(因为主实体类名 + 主键名会是 BlogBlogId,但 EF 会自动简化为 BlogId)。举个例子:如果主键属性名为BlogKey,那么,主实体名+主键属性名生成的外键将为(Blog BlogKey)

Cascade 级联

Restrict 限制

SetNull 为空

SetDefault 为默认

核心触发原则:最左前缀匹配

必须包含左前缀字段:查询条件中必须有索引的第一个字段 a,否则无法触发组合索引。

前缀连续不中断:从左到右使用索引字段,中间不能跳过。
示例:索引 idx(a,b,c),查询 where a=1 and c=2 只能触发 a 的索引;where a=1 and b=2 and c=3 可触发完整索引。

范围查询会中断后续字段:若某个字段使用范围查询(如 >、<、between),其右侧的字段无法再使用索引。
示例:索引 idx(a,b,c),查询 where a>1 and b=2 只能触发 a 的索引;where a=1 and b>2 and c=3 只能触发 a 和 b 的索引。

避免函数 / 运算操作索引字段

错误示例:where substr(a,1,2)='ab'(对 a 用函数)。
正确示例:where a like 'ab%'(前缀匹配,可触发 a 的索引)。

尽量实现 “索引覆盖”
查询的字段(select 后的列)若都在组合索引中,无需回表查询主键对应的完整数据,效率更高。
示例:索引 idx(a,b,c),查询 select a,b from table where a=1 and b=2 可实现索引覆盖;若查询 select a,b,d(d 不在索引中),则需回表。

假设表 user 有字段:id(主键)、name、age、gender,且建立了组合索引 idx(name, age)。
1. 触发回表的场景
查询语句:select id, name, age, gender from user where name='张三';
步骤 1:通过组合索引 idx(name, age),快速找到 name='张三' 对应的主键 id(比如 101)。
步骤 2:因为查询需要 gender 字段,但组合索引里没有 gender,所以必须用 id=101 去主键索引中查找,才能获取 gender 的值。
结论:这一步 “用 id 查 gender” 就是回表,多了一次索引查询,效率更低。

查询url或者其中之一会触发覆盖索引,提高查询速度,但如果为url和有不在包含列中的属性就不会触发覆盖索引,url ,address,title不会触发。

覆盖索引是sqlserver独有的,mysql用组合索引。

DBfirst

预加载include

显示加载 不推荐

对导航实体接着查询

延迟加载

调试,生成sql个数与访问次数相关N+1问题,访问role一次,加上你可能访问的N个导航实体

非代理模式

拆分查询-防止笛卡尔集爆炸

问题

关联查询会返回笛卡尔集,会包含左表重复数据

SELECT p.Id, p.PassportNo, b.Id, b.Amount, b.PassportId FROM Passports p LEFT JOIN Bills b ON p.Id = b.PassportId WHERE p.Id = 1;

拆分查询生成的 SQL(2 条独立语句)
第一条查 “护照”:

sql SELECT p.Id, p.PassportNo FROM Passports p WHERE p.Id = 1;


第二条查 “这个护照对应的所有账单”:

sql SELECT b.Id, b.Amount, b.PassportId FROM Bills b WHERE b.PassportId = 1;

结果集大小
第一条返回 1 条记录(仅护照信息)
第二条返回 1000 条记录(仅账单信息)
总记录数还是 1001,但没有重复的护照信息,结果集体积比之前小很多(节省了重复的 1000KB)。

全局配置

.AsSplitQuery()

AsQueryable()和AsEnumerable()

区别

AsEnumerable是查询所有字段,在内存中过滤条件

AsQueryable是按需索取,

sql注入

var user = "johndoe"; var blogs = await context.Blogs .FromSqlInterpolated($"SELECT * FROM Blogs WHERE Author = {user}") .ToListAsync(); 或者 var user = new SqlParameter("user", "johndoe"); var blogs = await context.Blogs .FromSqlRaw("SELECT * FROM Blogs WHERE Author = @user", user) .ToListAsync();

Add

using (var context = new AppDbContext()) { var newUser = new User { Id = 0, Name = "John" }; context.Users.Add(newUser); context.SaveChanges(); Console.WriteLine(newUser.Id); // 此时会输出数据库自动生成的自增 Id }
// 附加实体(默认状态为 Unchanged) context.Users.Attach(user); // 标记需要修改的属性(只更新该字段) context.Entry(user).Property(u => u.Name).IsModified = true; // 无需查询完整实体,只需构建包含主键的实体 var userToDelete = new User { Id = userId }; // 附加实体并标记为"删除"状态 context.Users.Attach(userToDelete); context.Entry(userToDelete).State = EntityState.Deleted; var newUser = new User { Name = userName, Age = age }; // 附加实体并标记为"新增"状态 context.Users.Attach(newUser); context.Entry(newUser).State = EntityState.Added; context.SaveChanges();

delete

// 直接构建嵌套子查询,EF Core 会转换为 NOT IN 语法 var deleteCount = context.Customers .Where(c => !context.Orders .Select(o => o.CustomerId) .Distinct() .Contains(c.CustomerId)) .ExecuteDelete(); sql: DELETE FROM [Customers] WHERE [CustomerId] NOT IN ( SELECT DISTINCT [o].[CustomerId] FROM [Orders] AS [o] ) ----------------------------------

update

int updatedCount = context.Students // 筛选条件:Tid 等于 李四老师的 Tid(子查询) .Where(s => s.Tid == context.Teachers .Where(t => t.Tname == "李四") .Select(t => t.Tid) .FirstOrDefault() // 对应 SQL 中的 = 子查询(确保子查询返回单行) ) // efcore3.0的批量更新:设置 Ssex 为 "女生" .ExecuteUpdate(setters => setters .SetProperty(s => s.Ssex, "女生") ); sql: UPDATE Students SET Ssex="女生" WHERE Tid = ( SELECT Tid From Teachers WHERE Tname = "李四" );
// 方法1:使用 Entity Framework Plus 扩展库 // https://entityframework-plus.net/ dbContext.Set<TableA>() .Where(a => dbContext.Set<TableB>().Any(b => b.Id == a.Id && a.Category == "某个条件")) .Update(a => new TableA { Column1 = dbContext.Set<TableB>() .Where(b => b.Id == a.Id) .Select(b => b.Column2) .FirstOrDefault() }); UPDATE [TableA] SET [Column1] = ( -- 子查询:从TableB匹配Id取值,无匹配则返回NULL SELECT TOP 1 [b].[Column2] FROM [TableB] AS [b] WHERE [b].[Id] = [TableA].[Id] ) -- 外层筛选条件:关联TableB且TableA.Category满足条件 WHERE EXISTS ( SELECT 1 FROM [TableB] AS [b] WHERE [b].[Id] = [TableA].[Id] AND [TableA].[Category] = N'某个条件' ); ----------------------------
// EF Core 7.0 及以上版本支持 await dbContext.Set<TableA>() .Where(a => a.Category == "某个条件") .ExecuteUpdateAsync(setters => setters .SetProperty(a => a.Column1, a => dbContext.Set<TableB>() .Where(b => b.Id == a.Id) .Select(b => b.Column2) .FirstOrDefault())); UPDATE [TableA] SET [Column1] = ( -- 子查询匹配TableB的Column2,FirstOrDefault()对应TOP 1 + 无匹配返回NULL SELECT TOP(1) [b].[Column2] FROM [TableB] AS [b] WHERE [b].[Id] = [TableA].[Id] ) -- 外层筛选条件:仅更新Category匹配的记录 WHERE [TableA].[Category] = N'某个条件'; --------------------------------------------------- 不推荐 // 配置日志输出到控制台 dbContext.Database.Log = sql => Console.WriteLine("执行的SQL:" + sql); // 执行你的代码 var query = from a in dbContext.Set<TableA>() join b in dbContext.Set<TableB>() on a.Id equals b.Id where a.Category == "某个条件" select new { A = a, BColumn2 = b.Column2 }; foreach (var item in await query.ToListAsync()) { item.A.Column1 = item.BColumn2; } await dbContext.SaveChangesAsync();

事务

当调用 SaveChanges() 时,EF Core 会自动创建一个事务,并将所有待执行的操作包含在该事务中。如果所有操作成功,则提交事务;如果有任何错误,则自动回滚。

using Microsoft.EntityFrameworkCore; using System; using System.Threading.Tasks; public class AppDbContext : DbContext { public DbSet<User> Users { get; set; } public DbSet<Order> Orders { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("Your_Connection_String"); // 对于SQLite:optionsBuilder.UseSqlite("Data Source=app.db"); } } public class User { public int Id { get; set; } public string Name { get; set; } } public class Order { public int Id { get; set; } public string Product { get; set; } public int UserId { get; set; } } public class TransactionService { // 同步事务示例 public void ProcessWithTransaction() { using (var context = new AppDbContext()) { // 开始事务 using (var transaction = context.Database.BeginTransaction()) { try { // 操作1 context.Users.Add(new User { Name = "Bob" }); context.SaveChanges(); // 操作2(假设UserId使用刚添加的用户ID) var newUserId = context.Users.First(u => u.Name == "Bob").Id; context.Orders.Add(new Order { Product = "Laptop", UserId = newUserId }); context.SaveChanges(); // 提交事务 transaction.Commit(); Console.WriteLine("所有操作已提交"); } catch (Exception ex) { // 回滚事务 transaction.Rollback(); Console.WriteLine($"事务已回滚: {ex.Message}"); } } } } // 异步事务示例 public async Task ProcessWithTransactionAsync() { using (var context = new AppDbContext()) { // 异步开始事务 using (var transaction = await context.Database.BeginTransactionAsync()) { try { context.Users.Add(new User { Name = "Charlie" }); await context.SaveChangesAsync(); var newUserId = context.Users.First(u => u.Name == "Charlie").Id; context.Orders.Add(new Order { Product = "Phone", UserId = newUserId }); await context.SaveChangesAsync(); // 异步提交 await transaction.CommitAsync(); Console.WriteLine("所有操作已异步提交"); } catch (Exception ex) { // 异步回滚 await transaction.RollbackAsync(); Console.WriteLine($"事务已异步回滚: {ex.Message}"); } } } } }
// 注意:需要数据库支持分布式事务(如SQL Server) using (var context1 = new AppDbContext1()) using (var context2 = new AppDbContext2()) { // 开启分布式事务 using (var transaction = await context1.Database.BeginTransactionAsync()) { try { // 操作第一个数据库 context1.Table1.Add(new Table1Entity()); await context1.SaveChangesAsync(); // 将第二个上下文关联到同一事务 await context2.Database.UseTransactionAsync(transaction.GetDbTransaction()); // 操作第二个数据库 context2.Table2.Add(new Table2Entity()); await context2.SaveChangesAsync(); // 提交分布式事务 await transaction.CommitAsync(); } catch { await transaction.RollbackAsync(); throw; } } }

建议不使用导航属性join查询

https://blog.csdn.net/cxnwb/article/details/120257199?fromshare=blogdetail&sharetype=blogdetail&sharerId=120257199&sharerefer=PC&sharesource=m0_68206177&sharefrom=from_link

// 联表查询 inner join:查询员工及其所属部门信息 var query = from emp in db.Employees join dept in db.Departments on emp.DepartmentId equals dept.Id // 手动指定关联条件 select new { 员工姓名 = emp.Name, 部门名称 = dept.DepartmentName, 薪资 = emp.Salary, 部门位置 = dept.Location }; // 执行查询(延迟加载,ToList() 触发数据库访问) var result = query.ToList(); SELECT [e].[Name] AS [员工姓名], [d].[DepartmentName] AS [部门名称], [e].[Salary] AS [薪资], [d].[Location] AS [部门位置] FROM [Employees] AS [e] INNER JOIN [Departments] AS [d] ON [e].[DepartmentId] = [d].[Id] 2 var query = db.Employees // 1. 外部表(左表):要查询的主表是员工表 .Join( inner: db.Departments, // 2. 内部表(右表):要关联的表是部门表 // 3. 外部表的关联键:员工表中用来匹配部门的字段(逻辑外键) outerKeySelector: emp => emp.DepartmentId, // 4. 内部表的关联键:部门表中用来匹配员工的字段(主键) innerKeySelector: dept => dept.Id, // 5. 结果投影:只取需要的字段,组装成新的匿名对象(避免返回全表字段) resultSelector: (emp, dept) => new { 员工姓名 = emp.Name, // 从员工表取姓名 部门名称 = dept.DepartmentName, // 从部门表取名称 薪资 = emp.Salary, // 从员工表取薪资 部门位置 = dept.Location // 从部门表取位置 } ); var result = query.ToList(); // 执行查询:ToList() 触发数据库访问,返回结果列表 ------------------------------------------------------------------------------------- // LEFT JOIN:查询所有员工,无部门则部门信息为 null var leftJoinQuery = from emp in db.Employees join dept in db.Departments on emp.DepartmentId equals dept.Id into tempDept from dept in tempDept.DefaultIfEmpty() select new { emp.Name, DepartmentName = dept?.DepartmentName ?? "无部门" }; 2. using (var db = new AppDbContext()) { // Lambda 语法实现 LEFT JOIN:查询所有员工 + 对应部门(无部门也保留员工) var query = db.Employees // 1. 第一步:GroupJoin(分组连接)—— 对应查询表达式的 join ... into .GroupJoin( inner: db.Departments, // 关联的右表(部门表) outerKeySelector: emp => emp.DepartmentId, // 左表(员工)的关联键 innerKeySelector: dept => dept.Id, // 右表(部门)的关联键 resultSelector: (emp, deptGroup) => new // 临时结果:员工 + 匹配的部门集合 { Employee = emp, DepartmentGroup = deptGroup // 存储当前员工匹配的所有部门(一对一则要么1条,要么空) } ) // 2. 第二步:SelectMany + DefaultIfEmpty()—— 扁平化集合,处理空值(左连接核心) .SelectMany( // 把部门集合“拆解开”(一对一场景下,拆后要么1个部门,要么空) collectionSelector: temp => temp.DepartmentGroup.DefaultIfEmpty(), // 最终结果投影:组装员工和部门的字段 resultSelector: (temp, dept) => new { 员工姓名 = temp.Employee.Name, 员工薪资 = temp.Employee.Salary, // 空值处理:无部门则显示“无部门” 部门名称 = dept?.DepartmentName ?? "无部门", 部门位置 = dept?.Location ?? "无" } ); // 执行查询(ToList() 触发数据库访问) var result = query.ToList(); }
public List<MenuModel> GetUserMenus(int userId) { using (var dbContext = new YourDbContext()) { // 方式1:多层Join关联(等价于SQL的多表JOIN) var menus = (from menu in dbContext.MenuModel join roleMenu in dbContext.RoleMenu on menu.MenuId equals roleMenu.MenuId join userRole in dbContext.UserRole on roleMenu.RoleId equals userRole.RoleId join role in dbContext.RoleInfo on userRole.RoleId equals role.RoleId where menu.State == 1 && role.State == 1 && userRole.UserId == userId select menu) .Distinct() // 去重(避免同一菜单被多个角色关联) .ToList(); // 方式2:子查询(更简洁,适合关联层级多的场景) var menus2 = (from menu in dbContext.MenuModel where menu.State == 1 && dbContext.RoleMenu.Any(rm => rm.MenuId == menu.MenuId && dbContext.UserRole.Any(ur => ur.RoleId == rm.RoleId && ur.UserId == userId && dbContext.RoleInfo.Any(r => r.RoleId == ur.RoleId && r.State == 1) ) ) select menu) .ToList(); //方式3 子查询 var query = from menu in dbContext.Set<MenuModel>() join roleMenu in dbContext.Set<RoleMenu>() on menu.MenuId equals roleMenu.MenuId where menu.State == 1 // 直接在查询中嵌套角色筛选条件 && (from ur in dbContext.Set<UserRole>() join role in dbContext.Set<RoleInfo>() on ur.RoleId equals role.RoleId where ur.UserId == userid && role.state == 1 select ur.RoleId ).Contains(roleMenu.RoleId) select menu; return menus; } } //1 SELECT DISTINCT [m].[MenuId], [m].[MenuName], [m].[State] FROM [MenuModel] AS [m] INNER JOIN [RoleMenu] AS [rm] ON [m].[MenuId] = [rm].[MenuId] INNER JOIN [UserRole] AS [ur] ON [rm].[RoleId] = [ur].[RoleId] INNER JOIN [RoleInfo] AS [r] ON [ur].[RoleId] = [r].[RoleId] WHERE [m].[State] = 1 AND [r].[State] = 1 AND [ur].[UserId] = @userId //2 SELECT [m].[MenuId], [m].[MenuName], [m].[State] FROM [MenuModel] AS [m] WHERE [m].[State] = 1 AND EXISTS ( SELECT 1 FROM [RoleMenu] AS [rm] WHERE [rm].[MenuId] = [m].[MenuId] AND EXISTS ( SELECT 1 FROM [UserRole] AS [ur] INNER JOIN [RoleInfo] AS [r] ON [ur].[RoleId] = [r].[RoleId] WHERE [ur].[RoleId] = [rm].[RoleId] AND [ur].[UserId] = @userId AND [r].[State] = 1 ) ) //3 SELECT [m].[MenuId], [m].[MenuName], [m].[State] FROM [MenuModel] AS [m] INNER JOIN [RoleMenu] AS [rm] ON [m].[MenuId] = [rm].[MenuId] WHERE [m].[State] = 1 AND [rm].[RoleId] IN ( SELECT [ur].[RoleId] FROM [UserRole] AS [ur] INNER JOIN [RoleInfo] AS [r] ON [ur].[RoleId] = [r].[RoleId] WHERE [ur].[UserId] = @userId AND [r].[State] = 1 )

left join例子

public async Task<List<UserModel>> GetUsersWithRoleAsync(string? keyword) { var userRoleData = from user in dbContext.Set<UserModel>() where string.IsNullOrEmpty(keyword) || user.RealName.Contains(keyword) || user.UserName.Contains(keyword) join userRole in dbContext.Set<UserRole>() on user.UserId equals userRole.UserId into tempUserRole from tur in tempUserRole.DefaultIfEmpty() join role in dbContext.Set<RoleInfo>() on tur.RoleId equals role.RoleId into tempRole from tr in tempRole.DefaultIfEmpty() where tr == null || tr.state == 1 select new { User = user, Role = tr }; var userRoleGroups = await userRoleData .GroupBy(item => item.User.UserId) .ToListAsync(); var resultUsers = new List<UserModel>(); foreach (var g in userRoleGroups) { var user = g.First().User; // 提取该用户的所有角色(过滤null) user.Roles = g.Select(item => item.Role) .Where(r => r != null) .Distinct() // 去重(避免重复角色) .ToList(); resultUsers.Add(user); } return resultUsers; } //----------------------------------------生成sql SELECT [s].[user_id], [s].[age], [s].[password], [s].[real_name], [s].[state], [s].[user_icon], [s].[user_name], [r].[role_id], [r].[role_name], [r].[state] FROM [system_users] AS [s] LEFT JOIN [user_role] AS [u] ON [s].[user_id] = [u].[user_id] LEFT JOIN [roles] AS [r] ON [u].[role_id] = [r].[role_id] WHERE [s].[state] = 1 AND (([r].[role_id] IS NULL) OR [r].[state] = 1) ORDER BY [s].[user_id]

复杂查询

using (var db = new AppDbContext()) { // 1. 定义查询参数(查询“北京”位置的部门下的员工) var targetLocation = "Beijing"; // 2. 执行参数化原生SQL,用dynamic接收结果 var query = db.Database .FromSqlInterpolated($@" SELECT e.Name AS EmployeeName, -- 员工姓名(别名:EmployeeName) d.DepartmentName AS DeptName, -- 部门名称(别名:DeptName) e.Salary AS Salary, -- 薪资(别名:Salary) d.Location AS DeptLocation -- 部门位置(别名:DeptLocation) FROM Employees e -- 员工表别名e INNER JOIN Departments d -- 内连接部门表,别名d ON e.DepartmentId = d.Id -- 关联条件:员工.DepartmentId = 部门.Id WHERE d.Location = {targetLocation} -- 参数化传入,自动防SQL注入 ") .ToList<dynamic>(); // 动态类型接收结果,不推荐,运行时才异常 // 3. 使用结果(运行时通过别名访问字段) foreach (var item in query) { Console.WriteLine($"Employee: {item.EmployeeName}, Dept: {item.DeptName}, Salary: {item.Salary:C}"); } } 推荐 using (var db = new AppDbContext()) { // 1. 定义强类型DTO:字段名、类型必须与SQL结果的别名、类型完全匹配 public class EmpDeptDto { public string EmployeeName { get; set; } // 对应SQL别名EmployeeName(字符串) public string DeptName { get; set; } // 对应SQL别名DeptName(字符串) public decimal Salary { get; set; } // 对应SQL别名Salary(decimal,与数据库字段类型一致) public string DeptLocation { get; set; } // 对应SQL别名DeptLocation(字符串) } // 2. 执行参数化SQL,映射到强类型DTO var query2 = db.Set<EmpDeptDto>() .FromSqlInterpolated($@" SELECT e.Name AS EmployeeName, d.DepartmentName AS DeptName, e.Salary AS Salary, d.Location AS DeptLocation FROM Employees e INNER JOIN Departments d ON e.DepartmentId = d.Id -- 可按需添加参数条件,示例:WHERE d.Location = {targetLocation} ") .ToList(); // 直接返回List<EmpDeptDto>强类型集合 // 3. 使用结果(编译时有字段提示,无拼写错误风险) foreach (var dto in query2) { Console.WriteLine($"Employee: {dto.EmployeeName}, Dept: {dto.DeptName}, Location: {dto.DeptLocation}"); } }

1对多=一个部门对应多个员工

你按照我这一篇文章的做法,虽然去掉外键,可以正常curd,但是当你下次迁移更新数据库会报错,找不到你外键的错误,需要手动删除你迁移类中dropForiegnKey函数和adddoriegnkey函数,但是每次迁移手动删除太麻烦了,你可以参考*我的另一篇*重写一下方法就行了

我的另一篇

public class Department { public long Id { get; set; } public string Name { get; set; } public List<Employee>? Employees { get; set; } = new List<Employee>();//--------$ } public class Employee { public long Id { get; set; } public string Name { get; set; } public Department? Dept { get; set; }//---------------$我没使用外键,所以可能为空加上“?” //意思就是,当我级联include查询员工表,员工表存在 //但他没有部门id为空,也会返回为空 public long? DepartmentId { get; set; } }

实体类配置

//部门 public void Configure(EntityTypeBuilder<Department> builder) { builder.Property(e => e.Name).IsRequired().HasMaxLength(20); builder.HasKey(e => e.Id); builder.HasMany<Employee>(e => e.Employees) .WithOne(e => e.Dept);//一个部门有多个员工(对应Employee类),一个员工对应一个部门 } //员工 public void Configure(EntityTypeBuilder<Employee> builder) { builder.Property(e => e.Name).HasMaxLength(20).IsRequired(); builder.Property(e => e.DepartmentId); builder.HasKey(e => e.Id); builder.HasOne<Department>(e => e.Dept) .WithMany(e => e.Employees); }

因为EFCore为保证数据一致性,如果你使用外键,当你删除部门时会带着该部门员工一起删除

现在企业中,以阿里的开发标准,不需要有外键,使用Efcor codefirst后,数据库自动有外键配置,需要手动删除,把外键id也重新设置可为空,

实验一下,以id删除部门

public Result DelDept(int id) { var dept = _context.DepartmentTable.Include(e => e.Employees) .Single(e => e.Id == id); if (dept != null) { _context.DepartmentTable.Remove(dept); _context.SaveChanges(); return Result.Success("ok"); } else { return Result.Error("删除失败"); } }

下面,程序返回了两端sql,意思是啥呢

意思是 部门表 left join员工表 找到部门id为符合条件后删除部门,然后置对应员工的DepartmentId为null

SELECT `t`.`Id`, `t`.`Name`, `e`.`Id`, `e`.`DepartmentId`, `e`.`Name` FROM ( SELECT `d`.`Id`, `d`.`Name` FROM `DepartmentTable` AS `d` WHERE `d`.`Id` = @__p_0 LIMIT 2 ) AS `t` LEFT JOIN `EmployeeTable` AS `e` ON `t`.`Id` = `e`.`DepartmentId` ORDER BY `t`.`Id` UPDATE `EmployeeTable` SET `DepartmentId` = @p0 WHERE `Id` = @p1; SELECT ROW_COUNT(); DELETE FROM `DepartmentTable` WHERE `Id` = @p2; SELECT ROW_COUNT();

把代码改成当删除部门id为1的科研部,同时删除该部门下的员工,看看是否成功

public Result DelDept(int id) { var dept = _context.DepartmentTable.Include(e => e.Employees).Single(e => e.Id == id); if (dept != null) { var emplist = dept.Employees; _context.EmployeeTable.RemoveRange(emplist);//删除该部门所有员工 _context.DepartmentTable.Remove(dept); _context.SaveChanges(); return Result.Success("ok"); } else { return Result.Error("删除失败"); } }

执行上面代码后,数据库的变化

http://www.rkmt.cn/news/1422514.html

相关文章:

  • Python开发者如何高效使用ChatGPT:从环境配置到实战应用
  • 基于Arduino与AI的Furby智能改造:硬件拆解与Python集成实践
  • 医药冷链运输的温湿度监控能做到无人值守吗?企业级Agent如何重塑效率
  • Simple Live终极指南:一站式跨平台直播聚合解决方案,5分钟搭建专属直播中心
  • Gemma-4-31B-it-assistant:Google开源多模态AI助手完全指南
  • 企业矩阵系统建设实践:从账号管理到AI内容协同
  • 2026年6月租房不收中介费指南,房东直租app省心租房攻略 - 资讯速览
  • 跨平台资源下载神器:3分钟快速掌握res-downloader完整教程
  • 告别视频下载烦恼:N_m3u8DL-CLI-SimpleG让你的在线视频保存变得如此简单
  • 5分钟掌握OBS LocalVocal:终极本地AI语音识别与实时字幕完整指南
  • 机器学习系统设计面试指南:从需求到上线的全流程拆解
  • 2026年4月流水槽模具企业推荐,拱形骨架护坡模板/化粪池模具/风电基础模板/检查井模具,流水槽模具企业哪家好 - 品牌推荐师
  • 如何3步解决岛屿设计难题:Happy Island Designer完整解决方案
  • 2026年6月河南郑州资质齐全的合同纠纷律师推荐:穆向明律师专业可靠服务好、经验丰富口碑好 - 焦点微观察
  • 2026 石家庄奢侈品回收正规店推荐|线下实体门店地址详情指南 - 薛定谔的梨花猫
  • 基于双ESP32的移动射频感知系统:Wi-Fi/蓝牙扫描与多源定位实践
  • 2026年国内Top5岩板品牌推荐!2026广东佛山最新排名出炉,大板智联梦想家优势突出 - 十大品牌榜
  • 2026昆明装修公司哪家好?真实案例验证家装避坑指南 - 商业新知
  • 三步让经典游戏重获新生:IPXWrapper拯救老游戏联机体验
  • 把闲置的魔百盒M401A变成智能家居大脑:保姆级Armbian+Docker+Home Assistant安装避坑指南
  • 宁波做停车棚厂家排行榜:宁波信创遮阳设备有限公司与行业实力厂商盘点 - 品牌评测官
  • 用Arduino与Plinko机制改造经典弹珠机:一个完整的STEAM创客项目实践
  • 2026年中山市应急灯厂家怎么选?国标认证/智能联动/全场景覆盖选购指南 - 资讯速览
  • 2026东莞中堂旧房翻新优选品牌盘点 本土实力企业赋能人居焕新 - 资讯速览
  • 2026 国内数字孪生企业实力纵览:覆盖工程工业与智慧城市的优质合作方 - 深度智识库
  • 2026年东莞塘厦优质装修企业盘点:本土实力品牌赋能品质人居升级 - 资讯速览
  • 2026 年 3 月青少年软编等考 C/C++ 一级测试题解析
  • dubbo | x-3 - [升级变更自检手册(xml)]
  • Cadence Schematic新手避坑指南:从鼠标滚轮到总线操作,这些快捷键让你效率翻倍
  • 夏日佳酿优选 口碑优质杨梅酒品牌选材工艺深度解析 - 品牌榜中榜