使用 SQL Server 模拟从 Web API 方法返回 IQueryable

Posted

技术标签:

【中文标题】使用 SQL Server 模拟从 Web API 方法返回 IQueryable【英文标题】:Returning IQueryable from Web API methods using SQL Server Impersonation 【发布时间】:2021-04-26 23:59:38 【问题描述】:

我有一个 .NET Framework 4.6.1 WebAPI,它公开端点以执行 CRUD 操作。我的客户希望在单个用户帐户的上下文中执行所有操作,因此我使用 SQL Server 模拟来使其工作(所有语句都写入审计文件,因此使用 SQL 模拟允许我们查看谁在执行该语句)。我从 NameIdentifier 声明中获取用户名,然后使用“EXECUTE AS USER”执行语句 命令。

通过 ID 检索单个记录的示例如下:

using (var transaction = SyncDbContext.Database.BeginTransaction())
         
   await SyncDbContext.Database.ExecuteSqlCommandAsync($"EXECUTE AS USER = 'UserName'");
   var data = await LookupAsync(id);
   await SyncDbContext.Database.ExecuteSqlCommandAsync("REVERT");
   transaction.Commit();
   return data;

这些语句需要在事务中执行,否则 SQL Server 会引发异常。此操作的审计文件将包含语句 EXECUTE AS USER = [USERNAME],[Statement], REVERT

除了返回 IQueryable 的端点外,所有 CRUD 操作都有效。我需要返回一个 IQueryable 以支持 oData 规范,该规范允许客户端传递查询字符串参数来修改查询(例如,包含 $top=10 的查询字符串参数将在 EF 查询中变为 .Take(10))。

以下是返回 IQueryable 的端点示例:

    [EnableQuery(MaxTop = 1000)]
    public async Task<IQueryable<Employee>> GetEmployees()
    
        using (var transaction = SyncDbContext.Database.BeginTransaction())
        
            await SyncDbContext.Database.ExecuteSqlCommandAsync($"EXECUTE AS USER = 'UserName'");
            var data = SyncDbContext.Employees.AsQueryable();
            await SyncDbContext.Database.ExecuteSqlCommandAsync("REVERT");
            transaction.Commit();
            return data;
        
    

问题在于,由于 iQueryable 的延迟执行,审计文件包含乱序的语句 - EXECUTE AS USER = [USERNAME], [REVERT], [STATEMENT]。有没有办法让 IQueryable 语句随事务“执行”,或者有更好的方法来实现我的目标?

【问题讨论】:

您能否澄清一下:“语句需要在事务中执行,否则 SQL Server 会引发异常。”您可能需要手动处理 SqlConnection 生命周期,而不是将其留给 DbContext。还有什么版本的EF? 【参考方案1】:

有没有办法让 IQueryable 语句随事务“执行”,或者有更好的方法来实现我的目标?

是的,只需使用 ToList() 将查询转换为实际的结果列表。这当然会将整个Employees表获取到客户端并在应用程序中进行过滤,这通常不是一个好主意。

另一种方法是采用一个参数来描述应该如何完成查询

[EnableQuery(MaxTop = 1000)]
public async Task<List<Employee>> GetEmployees(Func<IQueryable<Employee>, Task<List<Employee>>> filter)

    using (var transaction = SyncDbContext.Database.BeginTransaction())
    
        await SyncDbContext.Database.ExecuteSqlCommandAsync($"EXECUTE AS USER = 'UserName'");
        var data = await filter(SyncDbContext.Employees.AsQueryable());
        await SyncDbContext.Database.ExecuteSqlCommandAsync("REVERT");
        transaction.Commit();
        return data;
    

现在调用者可以指定他想要的任何类型的过滤,同时仍然确保查询在事务中运行。替代方法是使用 Func&lt;Employee, bool&gt; 在 Where 语句中使用。或者Func&lt;IQueryable&lt;Employee&gt;, Task&lt;T&gt;&gt;,以防调用者想要更具体。

【讨论】:

以上是关于使用 SQL Server 模拟从 Web API 方法返回 IQueryable的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 web api 将图像和文本从 android 应用程序发送到 sql server

用于使用 Azure SQL Server 数据的 C# Web API

无法从 NET Core 2.2 Web API 连接到 docker sql server

如何从 ASP.NET Core Web API 中的 SQL Server 存储过程中获取返回值

使用 json-server 来创建一个模拟 API

如何使用 API 将我的 Flutter 应用程序连接到本地 sql server 数据库?