Reputation: 39346
I'm trying to mapp an scalar Function of my DB that has a custom schema. This is how I'm registering the function in the context:
[DbFunction("ProjectMaterial_GetCostPrice","Project")]
public static decimal ProjectMaterial_GetCostPrice (int ProjectMaterialID, decimal ExtCost)
{
return 0;
}
I'm registering the Scalar function in a partial class of the context. And this is the Schema of the Scalar Function in the DB:
-- Select Project.ProjectDriver_GetCostPrice (5456921)
ALTER FUNCTION [Project].[ProjectMaterial_GetCostPrice] (@ProjectMaterialID int, @ExtCost money)
RETURNS MONEY
AS
Also I change the body of the method with a throw as the documentation suggest:
throw new NotSupportedException();
And it's thrown the exception instead of calling the function
This is how I call the function:
var newCostPrice= NsiteDBContext.ProjectMaterial_GetCostPrice(projectMaterial.ProjectMaterialId, projectMaterial.CostPrice.Value);
Upvotes: 4
Views: 3800
Reputation: 1764
I just encountered this use case myself and came up with the following solution. Suppose you have a user-defined scalar function dbo.GetCost(@MatID int, @ExtCost money)
that returns a decimal(18, 2)
. Then define a method in your DbContext
as follows:
[DbFunction]
public decimal GetCost(int matID, decimal extCost) {
NonEmptyTable.Take(1).Select(x => GetCost(matId, extCost))
.SingleOrDefault();
}
Usage:
decimal cost = db.GetCost(matID, extCost);
There's a bit of magic happening here, but it makes sense when you think it through. You're defining a C# method mapping to the database function as the EF Core docs suggest, but instead of the method body throwing an exception, it's actually invoking the same method in a Linq-to-SQL context, then returning the result (you could do this with two separate methods to avoid the appearance of recursion, but this is more concise).
What's important is that NonEmptyTable
is mapped to a table that is never empty, otherwise it will return a default value (you could also use .Single()
to throw an exception in this case, if preferred).
Upvotes: 2
Reputation: 21
As described in the previous answer, you can use functions, defined in that way, in the LINQ queries only. To call SQL scalar-valued functions directly, you should define it using DbContext.Database.ExecuteSqlCommand method. Like this, it should work:
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;
}
Upvotes: 1
Reputation: 15673
Using the call itself it throws the exception because it actually executes the C# code. The reason it is recommended to throw an exception is exactly this, to avoid inadvertent use, ie by directly calling it. That signature will be interpreted by the given LINQ provider and translate into the proper SQL statements.
To do so EF context needs to know how to use so some way might be
var items = await ctx.Materials.Select(c = > new {
Material= c,
CostPrice = ProjectMaterial_GetCostPrice(c.ProjectMaterialId, c.CostPrice.Value),
}).ToListAsync();
Now the ctx
object will know how to translate the ProjectMaterial_GetCostPrice
signature when it parses the expression trees.
Doing outside a select statement, even via the static call won't work as expected, and it throws that exception (to inform us this).
Upvotes: 3