简体   繁体   English

使用 SQL 服务器模拟从 Web API 方法返回 IQueryable

[英]Returning IQueryable from Web API methods using SQL Server Impersonation

I have a .NET Framework 4.6.1 WebAPI that exposes endpoints to perform CRUD operations.我有一个 .NET Framework 4.6.1 WebAPI,它公开端点以执行 CRUD 操作。 My client wants all operations performed in the context of an individual user account so I'm using SQL Server impersonation to make it work (all statements are written to an audit file so using SQL Impersonation allows us to see who is executing the statement).我的客户希望在单个用户帐户的上下文中执行所有操作,因此我使用 SQL 服务器模拟使其工作(所有语句都写入审计文件,因此使用 SQL 模拟允许我们查看谁在执行该语句)。 I get the username from the NameIdentifier claim and then execute statements using the “EXECUTE AS USER” command.我从 NameIdentifier 声明中获取用户名,然后使用“EXECUTE AS USER”命令执行语句。

An example to retrieve a single record via an ID is the following:通过 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;
}

The statements need to be performed inside a transaction otherwise SQL Server throws an exception.这些语句需要在事务中执行,否则 SQL 服务器会抛出异常。 The audit file for this operation would contain the statements EXECUTE AS USER = [USERNAME],[Statement], REVERT此操作的审计文件将包含语句 EXECUTE AS USER = [USERNAME],[Statement], REVERT

All CRUD operations work except for endpoints that return an IQueryable.除了返回 IQueryable 的端点外,所有 CRUD 操作都有效。 I need to return an IQueryable to support the oData specification which allow the client to pass query string arguments to modify the query (eg, a query string parameter containing $top=10 will become.Take(10) in the EF query).我需要返回一个 IQueryable 以支持 oData 规范,该规范允许客户端传递查询字符串 arguments 来修改查询(例如,包含 $top=10 的查询字符串参数将在 EF 查询中变为.Take(10))。

The following is example of an endpoint that returns an IQueryable:以下是返回 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;
        }
    }

The problem is that due to the deferred execution of iQueryable the audit file contains the statements out of order - EXECUTE AS USER = [USERNAME], [REVERT], [STATEMENT].问题在于,由于 iQueryable 的延迟执行,审计文件包含乱序的语句 - EXECUTE AS USER = [USERNAME]、[REVERT]、[STATEMENT]。 Is there a way to make the IQueryable statement “execute" with the transaction or a better way of achieving my goal?有没有办法让 IQueryable 语句在事务中“执行”,或者有更好的方法来实现我的目标?

Is there a way to make the IQueryable statement “execute" with the transaction or a better way of achieving my goal?有没有办法让 IQueryable 语句在事务中“执行”,或者有更好的方法来实现我的目标?

Yes, just use ToList() to convert the query to an actual list of results.是的,只需使用 ToList() 将查询转换为实际的结果列表。 This will of course fetch the entire Employees table to the client and do the filtering in the application, and that is usually not a good idea.这当然会将整个雇员表获取到客户端并在应用程序中进行过滤,这通常不是一个好主意。

An alternative would be to take a parameter that describes how the query should be done另一种方法是采用一个参数来描述应该如何完成查询

[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;
    }
}

Now the caller gets to specify whatever kind of filtering he wants, while still ensuring the query is run within the transaction.现在调用者可以指定他想要的任何类型的过滤,同时仍然确保查询在事务中运行。 Alternatives would be to take a Func<Employee, bool> for use in a Where-statement.替代方法是在 Where 语句中使用Func<Employee, bool> Or a Func<IQueryable<Employee>, Task<T>> in case the caller wants to be more specific.或者Func<IQueryable<Employee>, Task<T>>以防调用者想要更具体。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM