EF Core使用

附录

新手教程

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 提供编译时类型检查,减少运行时错误。
  • 延迟执行:合理利用延迟执行优化查询逻辑。
  • 导航属性:优先用 IncludeThenInclude 加载关联数据,避免 N+1 问题。
  • 性能:善用 AsNoTrackingSelect 投影、分页等技术。

示例:高效查询


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. 级联插入的典型场景

假设有两个实体 UserAccessToken,并且 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 是一个新对象(状态为 DetachedAdded),于是会自动将 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.Codenull,违反数据库约束。

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)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值