EF core 2.1中标量函数映射问题

4

我正在尝试映射一个具有自定义模式的数据库标量函数。这是我在上下文中注册该函数的方式:

 [DbFunction("ProjectMaterial_GetCostPrice","Project")]
 public static decimal ProjectMaterial_GetCostPrice (int ProjectMaterialID, decimal ExtCost)
 {
    return 0;
 }

我在上下文的部分类中注册标量函数。这是数据库中标量函数的模式:

-- Select Project.ProjectDriver_GetCostPrice (5456921)

ALTER FUNCTION [Project].[ProjectMaterial_GetCostPrice] (@ProjectMaterialID int, @ExtCost money) 
    RETURNS MONEY
AS

根据文档建议,我将方法的主体改为抛出异常:

throw new NotSupportedException();

它抛出了异常而不是调用函数。

这是我如何调用函数:

 var newCostPrice= NsiteDBContext.ProjectMaterial_GetCostPrice(projectMaterial.ProjectMaterialId, projectMaterial.CostPrice.Value);

你怎么使用它? - Adrian Iftode
好问题。让我更新一下。 - ocuenca
@AdrianIftode,我认为问题在于函数调用必须是linq查询的一部分,对吗? - ocuenca
该函数不应该这样使用,你需要在上下文中使用它与Select一起使用,然后上下文将知道如何将调用映射到实际的数据库函数。 - Adrian Iftode
没错。所以如果你确实需要查询一些材料,那么你也可以将该函数调用投影到一个MaterialModel中(也许)。 - Adrian Iftode
是的,我明白了,所以我需要为我的特定项目材料执行选择,然后使用标量函数进行投影。我之前并不知道这一点 ;) - ocuenca
3个回答

3
使用调用本身会引发异常,因为它实际上执行了C#代码。之所以建议抛出异常,正是为了避免意外使用,例如直接调用它。该签名将由给定的LINQ提供程序解释,并转换为适当的SQL语句。
为此,EF上下文需要知道如何使用,因此可能有一些方法。
var items = await ctx.Materials.Select(c = > new {
   Material= c,
   CostPrice = ProjectMaterial_GetCostPrice(c.ProjectMaterialId, c.CostPrice.Value),
}).ToListAsync();

现在,当ctx对象解析表达式树时,它将知道如何翻译ProjectMaterial_GetCostPrice的签名。
即使通过静态调用执行,也不能正确地在select语句之外进行操作,并且会抛出异常(以提示我们这一点)。

有没有其他方法可以做到这一点?我现在看到EF 2.2存在DBQuery类型。 - ocuenca
1
我看到它映射到视图,所以是的,你可以使用它,当然。而且你将把标量调用嵌入到视图本身中,甚至不需要在C#代码中映射函数。这取决于你的使用方式。不确定其他操作如何工作(删除、编辑)。 - Adrian Iftode

2

我刚刚遇到了这个用例,并想出了以下解决方案。假设你有一个用户定义的标量函数dbo.GetCost(@MatID int, @ExtCost money),它返回一个decimal(18, 2)。然后在你的DbContext中定义一个方法,如下所示:

[DbFunction]
public decimal GetCost(int matID, decimal extCost) {
    NonEmptyTable.Take(1).Select(x => GetCost(matId, extCost))
        .SingleOrDefault();
}

使用方法:

decimal cost = db.GetCost(matID, extCost);

这里有一些神奇的事情正在发生,但是当您仔细思考时就会有意义。您正在定义一个映射到数据库函数的C#方法,如EF Core文档所建议的那样,但是方法体不是抛出异常,而是在Linq-to-SQL上下文中调用相同的方法,然后返回结果(您可以使用两个单独的方法来避免递归的出现,但这更简洁)。

重要的是 NonEmptyTable 被映射到永远不为空的表格,否则它将返回默认值(如果需要,也可以使用.Single()在这种情况下抛出异常)。


1

如前面的回答所述,你只能在LINQ查询中使用以那种方式定义的函数。 要直接调用SQL标量值函数,你应该使用DbContext.Database.ExecuteSqlCommand方法来定义它。像这样,它应该可以工作:

public decimal ProjectMaterial_GetCostPrice(int ProjectMaterialID, decimal ExtCost)
{
    System.Data.SqlClient.SqlParameter resultParam =
        new System.Data.SqlClient.SqlParameter
    {
        ParameterName = "@resultCost",
        SqlDbType = System.Data.SqlDbType.Money,
        Direction = System.Data.ParameterDirection.Output
    };
    System.Data.SqlClient.SqlParameter parMaterialID =
        new System.Data.SqlClient.SqlParameter("@MaterialID", ProjectMaterialID);
    SqlParameter parExtCost =
        new System.Data.SqlClient.SqlParameter("@ExtCost", ExtCost);
    Database.ExecuteSqlCommand(
        "select @resultCost = [Project].[ProjectMaterial_GetCostPrice](@MaterialID, @ExtCost);",
        resultParam, parMaterialID, parExtCost);
    return (decimal)resultParam.Value;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接