【问题标题】:Use a Inline Table-Valued Functions with Linq and Entity Framework Core将内联表值函数与 Linq 和 Entity Framework Core 一起使用
【发布时间】:2018-10-24 18:26:43
【问题描述】:

我在 SQL Server 中创建了一个内联表值函数 (ITVF),它返回一个值表(为了讨论而简化了查询):

CREATE FUNCTION dbo.VehicleRepairStatus()
RETURNS TABLE
AS
   RETURN
       SELECT VehicleID, CurrentStatus 
       FROM VehicleRepairHistory
       ...

我可以在查询中引用:

SELECT   
    v.ID, v.Name,
    r.CurrentStatus
FROM  
    Vehicle v
LEFT OUTER JOIN 
    dbo.VehicleRepairStatus() r on v.ID = r.VehicleID

我希望能够在 Linq 查询中使用它:

var vehicles = await _databaseContext.Vehicles
    .Join() // join ITVF here?
    .Where(v => v.Type == 'Bus' )
    .OrderBy(v => v.Name)
    .ToAsyncList();

在某些时候,我可能会更改 ITVF 以包含一个参数:

CREATE FUNCTION dbo.VehicleRepairStatus(@id AS INT)
RETURNS TABLE
AS
RETURN

  SELECT VehicleID, CurrentStatus 
  FROM   VehicleRepairHistory
  ...
  WHERE  VehicleID = @id

并像标量一样调用:

SELECT   v.ID, v.Name
        ,(SELECT val FROM dbo.VehicleRepairStatus(v.ID)) AS CurrentStatus
FROM  Vehicle v

Linq 查询:

var vehicles = await _databaseContext.Vehicles
    .Select( )  // call ITVF here?
    .Where(v => v.Type == 'Bus' )
    .OrderBy(v => v.Name)
    .ToAsyncList();

这两种方法都可以吗?

【问题讨论】:

    标签: c# linq entity-framework-core asp.net-core-2.0 entity-framework-core-2.1


    【解决方案1】:

    是的,可以利用 EF Core 2.1 引入的 query types(从 EF Core 3.0 开始,与实体类型合并,现在称为无键实体类型)。以下是所需的步骤:

    首先,创建一个类来保存 TVF 记录(使用正确的数据类型对其进行更新):

    public class VehicleRepairStatus
    {
        public int VehicleID { get; set; }
        public int CurrentStatus { get; set; }
    }
    

    然后在你的OnModelCreating注册:

    EF Core 2.x:

    modelBuilder.Query<VehicleRepairStatus>();
    

    EF Core 3.x:

    modelBuilder.Entity<VehicleRepairStatus>().HasNoKey().ToView(null);
    

    然后结合使用QueryFromSql 方法(EF Core 2.x)从您的数据库上下文中公开它:

    public IQueryable<VehicleRepairStatus> VehicleRepairStatus(int id) => 
        Query<VehicleRepairStatus>().FromSql($"select * from VehicleRepairStatus({id})");
    

    SetFromSqlInterpolated(EF Core 3.x):

    public IQueryable<VehicleRepairStatus> VehicleRepairStatus(int id) => 
        Set<VehicleRepairStatus>().FromSqlInterpolated($"select * from VehicleRepairStatus({id})");
    

    仅此而已。

    现在您可以像使用任何其他 IQueryable&lt;T&gt; 返回方法一样在 LINQ 查询中使用它,例如:

    from v in db.Vehicles
    from r in db.VehicleRepairStatus(v.ID)
    select new { v.ID, v.Name, r.CurrentStatus }
    

    FromSql 方法中的“选择”使其可组合,因此整个查询被转换为 SQL 并在服务器端执行。

    更新:实际上,当用作相关子查询时,这不起作用,如上面的示例(请参阅Reference to an ITVF raises a "second operation started on this context before a previous operation completed" exception)。它只能在传递常量/变量参数时使用,例如

    from r in db.VehicleRepairStatus(123)
    ...
    

    查看链接中后续帖子的答案,了解相关查询场景的正确实施。

    【讨论】:

    • 这真的很好用!有没有办法编写将在控制台执行的 Linq 查询?由于 ITVF 与主模型位于不同的数据库中,因此我想确保查询按预期执行。
    • 不确定我是否关注。你能解释一下吗?
    • 当我使用两个数据库上下文时,从输出中可以看出第二个from 正在重复执行。我想看看 Linq 生成的 SQL。
    • 我将 ITVF 移动到与 Vehicles 相同的数据库中,并修改了上下文以支持它。当代码在model = await results.ToListAsync(); 行时,会产生错误:InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.。任何想法为什么会发生这种情况?我将查询分配给一个变量:var results = from v in db.Vehicles ...
    • @A.HADDAD 对此没有具体的文档。部分被Keyless Entity TypesRaw SQL Queries 覆盖
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-24
    • 1970-01-01
    • 2018-08-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多