EF Core使用SqlRaw添加实体后无法加载:值对象主键异常排查
这个问题的核心在于EF Core对自定义值类型的转换逻辑,以及SQLite存储Guid的默认行为,和你手动原生SQL插入的方式不匹配,具体拆解如下:
1. SQLite中Guid的存储格式不匹配
EF Core默认会将Guid类型(通过你的DeviceId值转换映射的Value属性)存储为SQLite的BLOB(二进制)类型。但你在AddDevice方法中,直接将Guid转为字符串拼接进SQL语句:
string sqlInsert = $"INSERT INTO \"Devices\" (\"Id\") VALUES ('{id}')";
这会导致数据库中存储的是字符串形式的Guid,而EF Core执行查询时,会把x.Id == new DeviceId(id)转换为对二进制Guid值的比较——字符串和二进制的Guid自然无法匹配,所以First()会返回null。
而你提到的两种正常情况也能佐证这一点:
- 当主键改为
int时,字符串和整数的转换逻辑简单,EF Core能正确处理,所以查询正常; - 通过
DbContext添加实体时,EF Core会自动按照配置的转换规则(将DeviceId.Value的Guid转为二进制)插入数据,存储格式和查询时的预期一致,所以能正常找到。
2. EF Core变更跟踪器未感知原生SQL插入的实体
另外,使用ExecuteSqlRaw执行原生SQL插入后,EF Core的变更跟踪器并不会自动同步这条新记录。虽然Assert.NotEmpty(context.Devices)能通过(因为它会查询数据库),但如果你的查询逻辑优先检查本地跟踪的实体(某些场景下),也可能出现不一致——不过这不是你当前断言失败的主要原因,核心还是存储格式问题。
针对这个问题,有两种可靠的修复方式:
方式一:使用参数化查询让EF Core处理Guid转换
不要手动拼接Guid字符串,而是用参数化查询让EF Core自动处理类型转换,确保存储格式和预期一致:
private void AddDevice(MyDbContext context, Guid id) { string sqlInsert = "INSERT INTO \"Devices\" (\"Id\") VALUES (@id)"; context.Database.ExecuteSqlRaw(sqlInsert, new SqlParameter("@id", id)); }
参数化查询不仅能解决格式匹配问题,还能避免SQL注入风险,是更安全的做法。
方式二:配置SQLite将Guid存储为TEXT类型
如果你需要保持原生SQL插入字符串格式的Guid,可以在DbContext配置中指定SQLite的Guid存储格式为TEXT:
services.AddDbContext<MyDbContext>(options => options.UseSqlite(connection, b => b.UseSqlGuidFormat(SqliteGuidFormat.Text)));
这样EF Core会将Guid以字符串形式存储和查询,和你手动插入的格式匹配,就能正常找到实体了。
内容的提问来源于stack exchange,提问作者Mojtaba Khooryani

