启用SQL Server行级安全后Entity Framework无法创建对象的问题咨询
解决EF6在SQL Server行级安全下插入报错的问题
核心原因
EF6默认在执行INSERT后会尝试查询刚插入的记录(用于更新实体状态、获取自增ID等),但行级安全策略限制当前用户无法读取这条刚插入的记录,导致EF检测到“影响行数为0”,触发乐观并发异常。
可行解决方案
1. 调整行级安全策略(优先推荐)
修改行级安全的谓词规则,允许用户读取自己插入的记录(比如通过CreatedBy字段关联当前数据库用户):
-- 创建行级安全谓词函数 CREATE FUNCTION dbo.UserAccessPredicate(@CreatedBy NVARCHAR(128), @CurrentUser NVARCHAR(128)) RETURNS TABLE WITH SCHEMABINDING AS RETURN SELECT 1 AS Result WHERE @CreatedBy = @CurrentUser; -- 应用安全策略到目标表 CREATE SECURITY POLICY UsersSecurityPolicy ADD FILTER PREDICATE dbo.UserAccessPredicate(CreatedBy, SUSER_SNAME()) ON dbo.Users WITH (STATE = ON);
这种方式既保留EF的原生功能,又符合常规业务逻辑(用户通常有权查看自己创建的数据),从根源上解决问题。
2. 重写SaveChanges,跳过EF插入后的验证(避免静默失败)
如果业务不允许用户读取自己插入的记录,可重写EF的SaveChanges方法,用原生SQL执行插入并手动确认结果,跳过EF的自动查询验证:
public override int SaveChanges() { var insertedEntities = ChangeTracker.Entries() .Where(e => e.State == EntityState.Added) .ToList(); foreach (var entry in insertedEntities) { // 示例:针对User表生成原生插入SQL,需根据实体动态调整字段 var sql = $"INSERT INTO Users (Name, Email, CreatedBy) VALUES (@Name, @Email, @CreatedBy);"; int rowsAffected = Database.ExecuteSqlCommand(sql, new SqlParameter("@Name", entry.Entity.Name), new SqlParameter("@Email", entry.Entity.Email), new SqlParameter("@CreatedBy", Environment.UserName)); // 手动验证插入是否成功,避免静默失败 if (rowsAffected == 0) { throw new InvalidOperationException("插入操作未生效,请检查权限配置"); } // 若有自增ID,可通过SCOPE_IDENTITY()获取并赋值给实体 var id = Database.SqlQuery<int>("SELECT SCOPE_IDENTITY();").Single(); entry.Entity.Id = id; // 将实体状态改为Unchanged,跳过EF后续的验证逻辑 entry.State = EntityState.Unchanged; } return base.SaveChanges(); }
3. 直接使用原生SQL插入(简单场景适用)
对于不需要EF跟踪实体的场景,直接调用ExecuteSqlCommand执行插入并手动检查影响行数:
var newUser = new User { Name = "Test", Email = "test@example.com", CreatedBy = Environment.UserName }; var sql = "INSERT INTO Users (Name, Email, CreatedBy) VALUES (@Name, @Email, @CreatedBy);"; int rowsAffected = db.Database.ExecuteSqlCommand(sql, new SqlParameter("@Name", newUser.Name), new SqlParameter("@Email", newUser.Email), new SqlParameter("@CreatedBy", newUser.CreatedBy)); if (rowsAffected == 0) { throw new Exception("插入失败,请检查权限配置"); }
总结
优先选择调整行级安全策略的方案,既能保留EF的原生特性,又符合业务逻辑。若业务限制必须隐藏用户自插数据,再采用重写SaveChanges或原生SQL插入的方式,务必手动验证插入结果,避免静默失败。
内容的提问来源于stack exchange,提问作者Doppelganger
相关产品推荐
相关产品推荐

