1.查询--- IQueryable 动态添加 Where 条件
最常用的方式是通过 IQueryable 动态添加 Where 条件,EF Core 会自动处理参数化和 SQL 生成,既安全又灵活。
场景示例:动态查询地址列表
假设需要实现一个 "地址查询接口",支持以下可选条件(用户可传可不传):
- 按
UserId筛选(必传) - 按
IsDefault筛选(可选:1 = 默认,0 = 非默认) - 按
Phone模糊搜索(可选) - 按
CreateTime时间范围筛选(可选)
cs
public async Task<List<Address>> QueryAddressesAsync(
long userId, // 必传条件
byte? isDefault = null, // 可选:是否默认地址
string phone = null, // 可选:手机号模糊搜索
DateTime? startTime = null, // 可选:创建开始时间
DateTime? endTime = null // 可选:创建结束时间
)
{
// 1. 初始化查询(必传条件先加上)
IQueryable<AddressBook> query = _waterDbContext.AddressBooks
.Where(addr => addr.UserId == userId); // 必传条件,先固定筛选
// 2. 动态添加可选条件(只在参数有值时添加)
if (isDefault.HasValue)
{
// 条件:IsDefault 等于传入的值(1或0)
query = query.Where(addr => addr.IsDefault == isDefault.Value);
}
if (!string.IsNullOrEmpty(phone))
{
// 条件:Phone 包含传入的字符串(模糊搜索)
query = query.Where(addr => addr.Phone.Contains(phone));
}
if (startTime.HasValue)
{
// 条件:CreateTime 大于等于开始时间
query = query.Where(addr => addr.CreateTime >= startTime.Value);
}
if (endTime.HasValue)
{
// 条件:CreateTime 小于等于结束时间
query = query.Where(addr => addr.CreateTime <= endTime.Value);
}
// 3. 执行查询并转换为领域对象(如果需要)
var addressBooks = await query.ToListAsync();
return _mapper.Map<List<Address>>(addressBooks);
}
核心原理
-
IQueryable 的 "延迟执行" 特性 :上述代码中,
query变量是IQueryable<AddressBook>类型,添加Where条件时不会立即执行查询 ,只会记录查询条件。直到调用ToListAsync()时,EF Core 才会根据所有条件生成最终的 SQL 并执行。 -
自动参数化 :EF Core 会将动态条件中的变量(如
isDefault.Value、phone)自动转换为 SQL 参数,避免 SQL 注入风险。例如,模糊搜索生成的 SQL 类似:sql
SELECT * FROM AddressBooks WHERE UserId = @p0 AND Phone LIKE '%' + @p1 + '%' -
条件拼接的灵活性 :可以根据任意逻辑动态添加条件(比如 "如果是管理员,则不筛选 UserId"),甚至通过循环添加批量条件(如
Where(addr => ids.Contains(addr.Id)))。
进阶:动态排序
如果需要支持 "按不同字段排序"(如按 CreateTime 升序 / 降序),可以这样扩展:
cs
// 新增排序参数
public async Task<List<Address>> QueryAddressesAsync(
// ... 其他参数 ...
string sortField = "CreateTime", // 排序字段(默认按创建时间)
bool isAscending = false // 是否升序(默认降序)
)
{
// ... 前面的条件拼接 ...
// 动态排序
query = sortField.ToLower() switch
{
"createtime" => isAscending ? query.OrderBy(addr => addr.CreateTime) : query.OrderByDescending(addr => addr.CreateTime),
"name" => isAscending ? query.OrderBy(addr => addr.Name) : query.OrderByDescending(addr => addr.Name),
_ => query.OrderByDescending(addr => addr.CreateTime) // 默认排序
};
// 执行查询
var addressBooks = await query.ToListAsync();
return _mapper.Map<List<Address>>(addressBooks);
}
何时需要 "原始动态 SQL"?
如果查询逻辑极其复杂(如多表联合查询、自定义聚合函数),可以使用 EF Core 的 FromSqlRaw 方法拼接原始 SQL,但必须严格参数化:
cs
// 原始 SQL 示例(谨慎使用,必须参数化!)
var sql = @"
SELECT * FROM AddressBooks
WHERE UserId = @UserId
{0} -- 动态条件占位符
";
var dynamicWhere = isDefault.HasValue ? "AND IsDefault = @IsDefault" : "";
var finalSql = string.Format(sql, dynamicWhere);
var addressBooks = await _waterDbContext.AddressBooks
.FromSqlRaw(finalSql,
new SqlParameter("@UserId", userId),
new SqlParameter("@IsDefault", isDefault ?? (object)DBNull.Value)
)
.ToListAsync();
总结
- 优先用
IQueryable动态拼接条件:安全、类型友好、无需手动写 SQL,适合 90% 以上的场景。 - 避免直接拼接 SQL 字符串:风险高,除非必须(如超复杂查询),且一定要用参数化。
- 核心技巧 :利用
IQueryable的延迟执行,按条件逐步添加Where/OrderBy,最后用ToListAsync执行。
2.修改
方式 1:动态更新部分字段(适合需要加载实体的场景)
cs
public async Task<bool> UpdateAddressAsync(AddressUpdateDto dto)
{
// 1. 查询要修改的实体
var address = await _waterDbContext.AddressBooks
.FirstOrDefaultAsync(addr => addr.Id == dto.AddressId);
if (address == null)
{
throw new BusinessException("地址不存在");
}
// 2. 动态更新字段(仅更新传入了值的字段)
if (!string.IsNullOrEmpty(dto.Name))
{
address.Name = dto.Name; // 姓名有值才更新
}
if (!string.IsNullOrEmpty(dto.Phone))
{
address.Phone = dto.Phone; // 手机号有值才更新
}
if (dto.IsDefault.HasValue)
{
address.IsDefault = dto.IsDefault.Value; // 是否默认有值才更新
}
// 3. 保存修改(EF 自动跟踪变化,只更新修改过的字段)
return await _waterDbContext.SaveChangesAsync() > 0;
}
方式 2:动态批量更新(无需加载实体,直接执行 SQL,EF Core 7.0+ 支持)
适合 "根据条件批量更新符合要求的记录",比如 "将用户所有非默认地址的 IsDeleted 设为 1"。
cs
public async Task<int> BatchUpdateAddressesAsync(long userId, bool setDeleted)
{
// 1. 构建动态条件(比如只更新非默认地址)
var query = _waterDbContext.AddressBooks
.Where(addr => addr.UserId == userId
&& addr.IsDefault == 0); // 只处理非默认地址
// 2. 动态执行更新(根据参数决定更新的值)
int updatedCount = await query
.ExecuteUpdateAsync(setters => setters
.SetProperty(addr => addr.IsDeleted, setDeleted ? 1 : 0) // 动态设置删除状态
.SetProperty(addr => addr.UpdateTime, DateTime.Now) // 强制更新时间戳
);
return updatedCount; // 返回更新的行数
}
3.删除
动态性体现在 "根据条件删除符合要求的记录",比如 "删除用户 30 天前的非默认地址"。
cs
public async Task<bool> DeleteAddressAsync(long addressId, long currentUserId)
{
// 1. 查询实体并验证条件(比如不允许删除默认地址)
var address = await _waterDbContext.AddressBooks
.FirstOrDefaultAsync(addr => addr.Id == addressId);
if (address == null)
{
throw new BusinessException("地址不存在");
}
// 2. 动态判断是否允许删除(例如:默认地址不允许删除)
if (address.IsDefault == 1)
{
throw new BusinessException("默认地址不允许删除,请先取消默认");
}
// 3. 执行删除
_waterDbContext.AddressBooks.Remove(address);
return await _waterDbContext.SaveChangesAsync() > 0;
}
方式 2:批量删除(根据动态条件,EF Core 7.0+ 支持)
适合 "一键清理符合条件的记录",比如 "删除用户所有已标记为删除的地址"。
cs
public async Task<int> BatchDeleteAddressesAsync(long userId, bool deleteExpired)
{
// 1. 构建动态删除条件
var query = _waterDbContext.AddressBooks
.Where(addr => addr.UserId == userId);
// 2. 动态添加额外条件(比如:如果需要删除过期地址,则加上时间条件)
if (deleteExpired)
{
query = query.Where(addr => addr.CreateTime < DateTime.Now.AddDays(-30));
}
else
{
query = query.Where(addr => addr.IsDeleted == 1); // 否则只删除已标记删除的
}
// 3. 执行批量删除(直接在数据库执行,不加载实体到内存)
int deletedCount = await query.ExecuteDeleteAsync();
return deletedCount; // 返回删除的行数
}
4.新增
动态性体现在 "根据条件决定实体的属性值",比如 "普通用户新增地址默认非默认,VIP 用户默认设为默认地址"。
cs
public async Task<long> AddAddressAsync(AddressAddDto dto, bool isVipUser)
{
// 1. 创建实体并设置基础属性
var address = new AddressBook
{
UserId = dto.UserId,
Name = dto.Name,
Phone = dto.Phone,
AddressDetail = dto.AddressDetail,
CreateTime = DateTime.Now
};
// 2. 动态设置条件属性(根据用户类型决定是否默认)
if (isVipUser)
{
address.IsDefault = 1; // VIP 用户新增地址默认设为默认
}
else
{
address.IsDefault = 0; // 普通用户默认非默认
}
// 3. 动态添加额外属性(比如仅当地址在特定城市时才设置)
if (dto.City == "北京" || dto.City == "上海")
{
address.IsBigCity = true; // 自定义字段:标记是否为大城市
}
// 4. 保存到数据库
_waterDbContext.AddressBooks.Add(address);
await _waterDbContext.SaveChangesAsync();
return address.Id; // 返回新增的主键
}
核心总结
- 动态新增 :通过条件判断给实体属性赋值,再调用
Add+SaveChanges。 - 动态修改 :
- 单实体:加载实体后,根据条件更新需要修改的字段,依赖 EF 自动跟踪。
- 批量:用
Where构建条件 +ExecuteUpdateAsync(高效,无需加载实体)。
- 动态删除 :
- 单实体:查询后判断条件,调用
Remove+SaveChanges。 - 批量:用
Where构建条件 +ExecuteDeleteAsync(高效)。
- 单实体:查询后判断条件,调用