为何在Select方法内调用GetSId方法返回值不变化?
EF Core 的 Select 默认是LINQ to Entities模式,会把 Lambda 表达式转换成 SQL 在数据库端执行。但你的 GetSId 是客户端 C# 方法,EF Core 无法将它转换成对应的 SQL 逻辑,只会在解析表达式树阶段一次性执行该方法获取一个固定值,然后把这个值填充到所有生成的 CrossingsResult 对象的 SId 字段中,这就是所有记录 SId 相同的原因。
而 foreach 循环能正常工作,是因为 ToList() 先把数据拉到了客户端,之后逐个调用 GetSId,每次调用都会单独执行 SQL 获取序列值,自然会递增。
解决方案1:直接在 LINQ 中调用数据库序列(推荐)
利用 EF Core 内置的序列操作方法,让 SQL 直接生成每行的序列值,不需要客户端方法:
private List<CrossingsResult> FormCrossingResult() { var crosswalks = _dbContext.Crosswalks .Select(x => new CrossingsResult { SId = EF.Functions.NextValueFor(_sequenceName), // 其他字段赋值逻辑 }).ToList(); return crosswalks; }
注意:EF.Functions.NextValueFor 需要 EF Core 3.0 及以上版本支持,序列名称要和 PostgreSQL 中定义的一致。
解决方案2:切换到客户端执行 Select
如果必须用现有的 GetSId,可以用 AsEnumerable() 提前把数据拉到客户端,后续的 Select 会变成 LINQ to Objects,逐个调用 GetSId:
private List<CrossingsResult> FormCrossingResult() { var d = _dbContext.Database; var crosswalks = _dbContext.Crosswalks .AsEnumerable() // 将查询切换到客户端执行 .Select(x => new CrossingsResult { SId = GetSId(d, _sequenceName), // 其他字段赋值逻辑 }).ToList(); return crosswalks; }
这种方式和 foreach 本质相同,但用 Select 完成赋值,避免了显式循环。不过数据量大时,AsEnumerable() 会把全量数据加载到内存,要注意性能。
解决方案3:SQL 层面关联序列值
直接通过 SQL 查询将序列值和原有数据关联,性能最优,但要注意 SQL 注入风险(如果序列名是外部传入的,必须参数化):
private List<CrossingsResult> FormCrossingResult() { var crosswalks = _dbContext.Crosswalks .FromSqlRaw($"SELECT *, nextval('{_sequenceName}') AS SId FROM crosswalks") .Select(row => new CrossingsResult { SId = row.SId, // 从 row 中映射其他字段 }).ToList(); return crosswalks; }
这里要确保 crosswalks 是数据库中对应的表名,字段映射正确。
内容的提问来源于stack exchange,提问作者Мади Маликов

