附录
新手教程
1. 安装 Entity Framework
在开始使用 EF 之前,需要在项目中安装 EF 的 NuGet 包。可以使用以下命令安装:
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
2. 在 App.config 中配置连接字符串
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="AdminContext"
connectionString="Data Source=.;Initial Catalog=SMDB;User ID=sa;Password=123456"
providerName="System.Data.SqlClient" />
</connectionStrings>
<!-- 其他配置 -->
</configuration>
3. 在 DbContext 类中使用连接字符串
在你的 DbContext 类中,重写 OnConfiguring 方法,使用 IConfiguration 来读取连接字符串。
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
public class MyDbContext : DbContext
{
private readonly IConfiguration _configuration;
public MyDbContext(IConfiguration configuration)
{
_configuration = configuration;
}
public DbSet<MyEntity> MyEntities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var connectionString = _configuration.GetConnectionString("DefaultConnection");
optionsBuilder.UseSqlServer(connectionString);
}
}
4. 在 Program.cs 或 Startup.cs 中配置依赖注入
在 Program.cs 或 Startup.cs 文件中,配置依赖注入,以便 DbContext 可以使用 IConfiguration。
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// 添加 DbContext 到依赖注入容器
builder.Services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
// 其他中间件配置...
app.Run();
5. 定义实体类
实体类是与数据库表对应的类。每个实体类的属性对应数据库表的列。
public class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
}
6. 使用 DbContext 进行数据库操作
可以使用 DbContext 类来进行数据库的增删改查操作。
6.1. 添加数据
using (var context = new MyDbContext())
{
var entity = new MyEntity { Name = "Test", CreatedDate = DateTime.Now };
context.MyEntities.Add(entity);
context.SaveChanges();
}
6.2. 查询数据
using (var context = new MyDbContext())
{
var entities = context.MyEntities.ToList();
foreach (var entity in entities)
{
Console.WriteLine($"{entity.Id} - {entity.Name} - {entity.CreatedDate}");
}
}
6.3. 更新数据
using (var context = new MyDbContext())
{
var entity = context.MyEntities.First();
entity.Name = "Updated Name";
context.SaveChanges();
}
6.4. 删除数据
using (var context = new MyDbContext())
{
var entity = context.MyEntities.First();
context.MyEntities.Remove(entity);
context.SaveChanges();
}
高级功能
LINQ 查询
1. 基础语法
简单查询
// 查询所有用户
var users = context.Users.ToList();
// 带条件的查询
var activeUsers = context.Users
.Where(u => u.IsActive)
.ToList();
排序和分页
var users = context.Users
.OrderBy(u => u.Name) // 按名称升序
.ThenByDescending(u => u.Age) // 再按年龄降序
.Skip(10) // 跳过前10条
.Take(5) // 取5条
.ToList();
投影(Select)
只查询需要的字段,提升性能:
var userNames = context.Users
.Select(u => new { u.Id, u.Name })
.ToList();
2. 延迟执行 vs 立即执行
- 延迟执行:LINQ 查询在调用
ToList()
、FirstOrDefault()
等方法前不会实际执行。 - 立即执行:调用
ToList()
、Count()
、Any()
等方法会立即触发 SQL 查询。
示例:
var query = context.Users.Where(u => u.Age > 18); // 未执行
// 触发查询
var adults = query.ToList();
3. 导航属性与关联查询
Include 加载关联数据
// 加载用户的订单(一对多)
var usersWithOrders = context.Users
.Include(u => u.Orders) // 加载所有订单
.ToList();
// 多层嵌套加载(例如用户 -> 订单 -> 订单明细)
var users = context.Users
.Include(u => u.Orders)
.ThenInclude(o => o.OrderDetails)
.ToList();
显式加载(Explicit Loading)
var user = context.Users.First(u => u.Id == 1);
context.Entry(user)
.Collection(u => u.Orders) // 加载集合
.Load();
LINQ 连接查询(Join)
var query =
from user in context.Users
join order in context.Orders
on user.Id equals order.UserId
select new { user.Name, order.TotalAmount };
4. 复杂查询与函数
条件动态拼接
IQueryable<User> query = context.Users;
if (searchName != null)
{
query = query.Where(u => u.Name.Contains(searchName));
}
if (minAge > 0)
{
query = query.Where(u => u.Age >= minAge);
}
var result = query.ToList();
使用 SQL 函数
// 使用 EF.Functions 调用数据库函数
var users = context.Users
.Where(u => EF.Functions.Like(u.Name, "A%"))
.ToList();
5. 性能优化
避免 N+1 查询问题
// 错误示例:循环中多次查询
var users = context.Users.ToList();
foreach (var user in users)
{
var orders = context.Orders.Where(o => o.UserId == user.Id).ToList(); // 每次循环触发一次查询
}
// 正确做法:一次性加载关联数据
var users = context.Users.Include(u => u.Orders).ToList();
AsNoTracking
查询时禁用变更跟踪,提升性能:
var users = context.Users
.AsNoTracking()
.ToList();
Select 投影优化
只查询需要的字段:
var userDtos = context.Users
.Select(u => new UserDto { Id = u.Id, Name = u.Name })
.ToList();
6. 执行原生 SQL
当 LINQ 无法满足需求时,直接执行 SQL:
var users = context.Users
.FromSqlRaw("SELECT * FROM Users WHERE Age > {0}", 18)
.ToList();
7. 常见错误
客户端评估
// 错误:EF Core 无法将此条件转换为 SQL,会在客户端过滤
var users = context.Users
.ToList() // 先加载所有数据到内存
.Where(u => u.Name.Length > 5); // 在内存中过滤
解决方案:确保查询逻辑可以在服务端转换为 SQL。
总结
- 强类型:LINQ 提供编译时类型检查,减少运行时错误。
- 延迟执行:合理利用延迟执行优化查询逻辑。
- 导航属性:优先用
Include
或ThenInclude
加载关联数据,避免 N+1 问题。 - 性能:善用
AsNoTracking
、Select
投影、分页等技术。
示例:高效查询
var result = context.Users
.Where(u => u.IsActive)
.OrderBy(u => u.Name)
.Select(u => new { u.Id, u.Name })
.Skip(0)
.Take(10)
.AsNoTracking()
.ToList();
理解这些核心概念后,你可以更高效地使用 EF Core LINQ 构建健壮的数据库查询!
级联插入
关系映射
EF 支持一对多、多对多等关系映射。
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
配置 Fluent API
可以使用 Fluent API 来配置实体和关系。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(b => b.Posts)
.WithOne(p => p.Blog)
.HasForeignKey(p => p.BlogId);
}
1. EF Core 的“级联插入”详解
在 EF Core 中,级联插入(Cascade Insert) 指的是当保存一个实体时,如果该实体引用了其他关联实体(例如导航属性),EF Core 会根据关联实体的状态,自动决定是否将这些关联实体一起插入到数据库。这一行为是 EF Core 关系映射和变更跟踪机制的一部分,但如果不理解其规则,可能导致意外错误(例如插入本不该插入的关联实体)。
1. 级联插入的典型场景
假设有两个实体 User
和 AccessToken
,并且 AccessToken
通过导航属性引用 User
:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public class AccessToken
{
public int Id { get; set; }
public string Token { get; set; }
public User User { get; set; } // 导航属性
}
当执行以下代码时:
var user = new User { Name = "Alice" }; // 未设置 Id,且未添加到 DbContext
var token = new AccessToken { User = user, Token = "abc123" };
dbContext.Add(token); // 仅添加 token
dbContext.SaveChanges();
- 问题:
user
是一个新对象,未被跟踪(未通过dbContext.Users.Add(user)
显式添加),但token
引用了它。 - EF Core 的行为:
EF Core 发现token.User
是一个新对象(状态为Detached
或Added
),于是会自动将user
插入数据库,再插入token
,并建立外键关系。
这就是 级联插入。
2. 为什么级联插入会导致错误?
如果关联实体(如 User
)的某些字段未正确赋值,级联插入会直接报错。例如:
public class User
{
public int Id { get; set; }
[Required]
public string Code { get; set; } // 必填字段
public string Name { get; set; }
}
// 创建 User 对象时未赋值 Code
var user = new User { Name = "Alice" }; // Code 为 null
var token = new AccessToken { User = user, Token = "abc123" };
dbContext.Add(token);
dbContext.SaveChanges(); // 报错:Column 'Code' cannot be null
- 根因:级联插入尝试插入
User
,但User.Code
为null
,违反数据库约束。
3. 如何避免意外的级联插入?
1.1. 方法 1:显式管理关联实体的状态
- 场景:如果
User
已存在于数据库,不应插入新User
。 - 解决方案:告诉 EF Core 该
User
是已存在的实体(状态为Unchanged
)。
var user = new User { Id = 1, Name = "Alice" }; // 假设 Id=1 的 User 已存在
// 标记 User 为未更改,避免插入
dbContext.Entry(user).State = EntityState.Unchanged;
var token = new AccessToken { User = user, Token = "abc123" };
dbContext.Add(token);
dbContext.SaveChanges(); // 仅插入 token,不操作 User
1.2. 方法 2:使用外键代替导航属性
- 场景:避免通过导航属性引用整个实体,直接使用外键字段。
- 实体定义:
public class AccessToken
{
public int Id { get; set; }
public string Token { get; set; }
public int UserId { get; set; } // 外键字段
}
- 代码:
var token = new AccessToken { UserId = 1, Token = "abc123" }; // 直接赋值外键
dbContext.Add(token);
dbContext.SaveChanges(); // 不会操作 User 表
1.3. 方法 3:禁用特定关系的级联行为
在模型配置中关闭级联插入:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AccessToken>()
.HasOne(t => t.User)
.WithMany()
.OnDelete(DeleteBehavior.NoAction); // 关闭级联删除(但插入行为仍需通过状态管理)
}
4. 关键总结
- 级联插入:EF Core 会尝试自动插入被引用且状态为
Added
的关联实体。 - 风险点:若关联实体未正确初始化(如必填字段为
null
),会导致插入失败。 - 解决方案:
-
- 显式管理关联实体的状态(如标记为
Unchanged
)。 - 优先使用外键字段而非导航属性。
- 确保关联实体已正确赋值(如必填字段不为
null
)。
- 显式管理关联实体的状态(如标记为