Entity Framework 核心 3.1 存储过程可选参数不起作用
Entity Framework Core 3.1 stored procedure optional parameters not working
[已解决](见尾)
我花了一天的大部分时间寻找一个没有运气的解决方案。
- EF 核心 3.1
- Visual Studio 2019
- SQL 服务器 2017
- .网络核心 3.1
我一直无法找到一种解决方案,可以使用可选参数从 .Net Core 3.1 调用存储过程。我已经阅读了从 SO 到博客的数十篇文章,但没有任何效果。
这是我的 C# 代码,用于调用存储过程的方法,并进行了所有尝试以实现此目的:
public List<BudgetWorkflowStatusByDepartment> GetBudgetWorkflowStatusByDepartment(int? companyId, int? departmentId, int? locationId, int? sublocationId)
{
//var compId = new SqlParameter("@CompanyId", companyId);
//compId.Value = (object)companyId ?? SqlInt32.Null;
//var deptId = new SqlParameter("@DepartmentId", departmentId);
//deptId.Value = (object)departmentId ?? SqlInt32.Null;
//var locId = new SqlParameter("@LocationId", locationId);
//locId.Value = (object)locationId ?? SqlInt32.Null;
//var subId = new SqlParameter("@SubLocationId", sublocationId);
//subId.Value = (object)sublocationId ?? SqlInt32.Null;
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment @CompanyID, @DepartmentID, @LocationID, @SubLocationID", compId, deptId, locId, subId).ToList();
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment {0}, {1}, {2}, {3}", companyId, departmentId, locationId, companyId).ToList();
// Error Must declare the scalar variable "@CompanyID".
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlRaw($"EXEC mis.BudgetWorkflowStatusByDepartment @CompanyID={compId}, @DepartmentID={deptId}, @LocationID={locId}, @SubLocationID={subId}").ToList();
// Error Must declare the scalar variable "@CompanyID".
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw($"EXEC mis.BudgetWorkflowStatusByDepartment {compId}, {deptId}, {locId}, {subId}").ToList();
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment @CompanyID={compId}, @DepartmentID={deptId}, @LocationID={locId}, @SubLocationID={subId}").ToList();
return result;
}
None 这些尝试都奏效了。错误范围为:
Data is Null. This method or property cannot be called on Null values.
...到...
Must declare the scalar variable "@compId".
...当我尝试一些很可能只是不正确的东西时,为了让它工作而绝望地尝试了一些其他错误。
这是存储过程本身的签名,表明所有参数都可以为空。
ALTER PROCEDURE [mis].[BudgetWorkflowStatusByDepartment]
(@CompanyID int = null,
@DepartmentID int = null,
@LocationID int = null,
@SubLocationID int = null)
令人沮丧的是,此存储过程在 SQL 服务器中,在带或不带参数的 LinqPad 中都有效。它在我的代码中仅适用于所有参数,但如果有任何参数为空,则会引发错误。
任何关于如何让函数使用可选参数的见解,我都非常感激。
编辑:2020 年 8 月 24 日
根据新建议,我尝试了它们,但出于某种原因它们仍然不起作用。
我已经在 SSMS 中使用所有四个参数直接测试了存储过程,它可以正常工作。然后我一个一个地删除了最后一个参数并在每个参数被删除时对其进行了测试并且它可以正常工作 returning 正确的数据。
当我使用 Api 和对此方法的服务调用进行相同的测试时,4 个参数有效,3 个参数有效,但 2 个参数、1 个参数和无参数均无效!我不明白为什么不。
这是我的新尝试:
/// New attempts as of: 8/24/2020
// Error Data is Null. This method or property cannot be called on Null values.
//var compId = new SqlParameter("@CompanyID", companyId);
//compId.Value = (object)companyId ?? DBNull.Value;
//var deptId = new SqlParameter("@DepartmentID", departmentId);
//deptId.Value = (object)departmentId ?? DBNull.Value;
//var locId = new SqlParameter("@LocationID", locationId);
//locId.Value = (object)locationId ?? DBNull.Value;
//var subId = new SqlParameter("@SubLocationID", sublocationId);
//subId.Value = (object)sublocationId ?? DBNull.Value;
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment {compId}, {deptId}, {locId}, {subId}").ToList();
// Error Data is Null. This method or property cannot be called on Null values.
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment {companyId}, {departmentId}, {locationId}, {sublocationId}").ToList();
//var compId = new SqlParameter("@CompanyID", companyId) { IsNullable = true };
//compId.Value = (object)companyId ?? DBNull.Value;
//var deptId = new SqlParameter("@DepartmentID", departmentId) { IsNullable = true };
//deptId.Value = (object)departmentId ?? DBNull.Value;
//var locId = new SqlParameter("@LocationID", locationId) { IsNullable = true };
//locId.Value = (object)locationId ?? DBNull.Value;
//var subId = new SqlParameter("@SubLocationID", sublocationId) { IsNullable = true };
//subId.Value = (object)sublocationId ?? DBNull.Value;
//// Error Data is Null. This method or property cannot be called on Null values.
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment @CompanyID = @CompanyID, @DepartmentID = @DepartmentID, @LocationID = @LocationID, @SubLocationID = @SubLocationID", compId, deptId, locId, subId)
// .ToList();
var compId = new SqlParameter("@CompanyID", companyId) { IsNullable = true };
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId) { IsNullable = true };
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId) { IsNullable = true };
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId) { IsNullable = true };
subId.Value = (object)sublocationId ?? DBNull.Value;
// Error Data is Null. This method or property cannot be called on Null values.
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment @CompanyID, @DepartmentID, @LocationID, @SubLocationID", compId, deptId, locId, subId)
.ToList();
我想我应该将我的 DbContext 属性 设置包含在这个存储过程中作为附加信息。同样的模式用于另外两个存储过程,它们可以工作,但不需要可选参数。
public DbSet<BudgetWorkflowStatusByDepartment> BudgetWorkflowStatusByDepartment { get; set; }
并且在 OnModelCreating() 方法中我设置了 HasNoKey()
方法。
modelBuilder.Entity<BudgetWorkflowStatusByDepartment>().HasNoKey();
感谢大家的建议。
我的解决方法是告诉 DBA 更改存储过程以一起删除参数,我将只对结果做一个 .Where()
子句。
幸运的是,对于此特定功能,它不会 return 超过 200 行,但它会 return 许多列,但这仍然不应该很费力。
已解决原因
这是我的错。我有一些 int
属性需要是可为空的整数。一旦我这样做了,它就起作用了,而且它似乎适用于大多数解决方案。
重要的是要注意那些可能为 null 的属性并使它们可以为 null,这样您就不会成为我的问题的牺牲品,我花了一整天的时间才解决。
我想既然我正在尝试所有这些我以前没有做过的事情(使用可选参数和 .Net Core 3.1 存储过程),我会用各种选项记录我的结果处理这个。
这些是关于从 .Net Core 3.1 执行存储过程时有效和无效的一些选项。
对于这些示例,这将是 .Net Core 方法的签名。
public List<BudgetWorkflowStatusByDepartment> GetBudgetWorkflowStatusByDepartment
(int? companyId, int? departmentId, int? locationId, int? sublocationId)
{
...
}
所有参数都是可选的。以下示例将是您可以在此方法中使用的代码。
这是存储过程的签名。
ALTER procedure mis.BudgetWorkflowStatusByDepartment_Filtered (
@CompanyID int = null
, @DepartmentID int = null
, @LocationID int = null
, @SubLocationID int = null
)
工作示例
以下示例演示如何使用 FromSqlRaw
或 FromSqlInterpolated
方法来执行存储过程。
来自Sql原始方法
此示例仅创建 SqlParameter
个对象并将值设置为传入值或 DbNull
。这很重要,因为 null
在这种情况下不起作用。 (参见下面的非工作示例)
// Works
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID, @DepartmentID, @LocationID, @SubLocationID", compId, deptId, locId, subId).ToList();
以下通过包含 SqlParameter
对象的 IsNullable=true
属性 来实现。
// Works
var compId = new SqlParameter("@CompanyID", companyId) { IsNullable = true };
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId) { IsNullable = true };
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId) { IsNullable = true };
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId) { IsNullable = true };
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID = @CompanyID, @DepartmentID = @DepartmentID, @LocationID = @LocationID, @SubLocationID = @SubLocationID", compId, deptId, locId, subId)
.ToList();
下面和上面类似,只是稍微简写的语法方法。
// Works
var compId = new SqlParameter("@CompanyID", companyId) { IsNullable = true };
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId) { IsNullable = true };
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId) { IsNullable = true };
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId) { IsNullable = true };
subId.Value = (object)sublocationId ?? DBNull.Value;
// Works
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID, @DepartmentID, @LocationID, @SubLocationID", compId, deptId, locId, subId)
.ToList();
从Sql插值
以下有效,因为它使用 .FromSqlInterpolated()
方法,然后占位符语法将起作用。
// Works
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID={compId}, @DepartmentID={deptId}, @LocationID={locId}, @SubLocationID={subId}").ToList();
下面与上面类似,但它使用了稍微简写的语法。
// Works
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered {compId}, {deptId}, {locId}, {subId}").ToList();
以下是速记语法的终极。使用插值方法,您可以直接使用传入的变量,而不必创建 SqlParameter
个对象。
// Works
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered {companyId}, {departmentId}, {locationId}, {sublocationId}").ToList();
非工作示例
下面的代码return是一个空数组,这是不正确的。代码的这种参数化只能以这种方式与“插值”Sql 方法一起使用。
// Returns empty array
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment_Filtered {0}, {1}, {2}, {3}", companyId, departmentId, locationId, companyId).ToList();
结果 - 一个空数组,这是不正确的:
[]
下面的方法在FromSqlRaw
方法的语法中是不正确的。字符串中的参数想要使用插值,但这是该语法的错误方法。
// Error Must declare the scalar variable "@CompanyID".
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID={compId}, @DepartmentID={deptId}, @LocationID={locId}, @SubLocationID={subId}").ToList();
错误:
Must declare the scalar variable "@CompanyID".
以下也不正确。字符串中的参数不能以这种方式填充。
// Error Must declare the scalar variable "@CompanyID".
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered {compId}, {deptId}, {locId}, {subId}").ToList();
错误:
Must declare the scalar variable "@CompanyID".
最后,这也不起作用,因为 ??
运算符将空值设置为 null
而不是 DbNull.Value
。
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? null;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? null;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? null;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? null;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID, @DepartmentID, @LocationID, @SubLocationID", compId, deptId, locId, subId).ToList();
错误:
The parameterized query '(@CompanyID nvarchar(4000),@DepartmentID nvarchar(4000),@Locatio' expects the parameter '@CompanyID', which was not supplied.
我只是 re-created 一个类似的设置(所有你拥有的相同版本),以下 one-liner 对我有用:
public List<BudgetWorkflowStatusByDepartment> GetBudgetWorkflowStatusByDepartment(int? companyId, int? departmentId, int? locationId, int? sublocationId)
=> context.BudgetWorkflowStatusByDepartment
.FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment {companyId}, {departmentId}, {locationId}, {subLocationId}").ToList();
注意,因为我正在使用 .FromSqlInterpolated()
调用,所以不需要创建 SqlParameter
s - int?
可以直接传入,因为这是一个安全的根据 docs 调用,null
值得到正确处理。
我看到你也试过这个调用,但看起来你传递的是 SqlParameter
s,而不是数据本身 - 试试我的版本?
经过进一步测试,我让你的版本可以工作,稍作修改,并将 SqlParameter.IsNullable
属性 设置为 true
:
public List<BudgetWorkflowStatusByDepartment> GetBudgetWorkflowStatusByDepartment(int? companyId, int? departmentId, int? locationId, int? sublocationId)
{
var compId = new SqlParameter("@CompanyID", companyId) { IsNullable = true };
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId) { IsNullable = true };
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId) { IsNullable = true };
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId) { IsNullable = true };
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment @CompanyID = @CompanyID, @DepartmentID = @DepartmentID, @LocationID = @LocationID, @SubLocationID = @SubLocationID", compId, deptId, locId, subId)
.ToList();
return result;
}
任你选择 - 我个人喜欢 one-liner :)
我发现使用 dotnetcore 获取命名参数(以及可选参数)的唯一方法是使用以下方法:
_context.Database.ExecuteSqlRaw("EXEC mysproc @param1={0}, @param2={1}"
,param1
,param2);
... 其中 param1 和 param2 是 Microsoft.Data.SqlClient.SqlParameter 类型(这很重要,因为我发现 System.Data.SqlClient.SqlParamater 类型不会产生编译错误但会导致令人困惑的异常运行次)。
虽然这始终有效,即使使用输出参数(在参数占位符后添加“out”),当您的代码可能使用不同的参数集提供不同的结果时,这是一种非常不灵活的创建语句的方式.我的一个用例有 23 个我可能会提供的参数,其中只有三个是必需的。硬编码 ExecuteSqlRaw 语句不是一种选择。
从那以后,我想出了一个有用的方法,我在我的控制器、页面等中使用了注入 DbContext _context 的地方:
private void ExecuteStoredProcedure(
string StoredProcedureName
,List<SqlParameter> parameters)
{
StringBuilder stringBuilder = null;
int paramNum = 0;
// Build the T-SQL statement to enable named paramaters
foreach (SqlParameter param in parameters)
{
string direction = param.Direction == ParameterDirection.Output ? " out" : "";
if (stringBuilder == null)
{
stringBuilder = new StringBuilder(string.Format(CultureInfo.CurrentCulture
, "EXEC {0} {1}={{{2}}}{3}"
, StoredProcedureName
, param.ParameterName
, paramNum++
, direction));
}
else
{
stringBuilder.Append(string.Format(CultureInfo.CurrentCulture
, ",{0}={{{1}}}{2}"
, param.ParameterName
, paramNum++
, direction));
}
}
try
{
_context.Database.ExecuteSqlRaw(
stringBuilder.ToString()
,parameters.ToArray());
}
catch (Exception ex)
{
throw;
}
}
该方法采用任意顺序的 SqlParameters 列表。请注意,列表在传递给 ExecuteSqlRaw 之前会转换为 SqlParamater 数组。它在技术上不是一个“参数数组”,但我还没有看到它失败。您可能还想将 DBContext 或 DatabaseFacade 传递给该方法。
我通常按以下方式构造参数列表。 ParameterName 属性 必须匹配它将提供的存储过程参数的名称。我排除了 inclusion/exclusion 个参数的任何逻辑。
List<SqlParameter> parameters = new List<SqlParameter>();
parameters.Add(new SqlParameter()
{
ParameterName = "@MyOutputParam",
DbType = System.Data.DbType.Int64,
Direction = System.Data.ParameterDirection.Output
});
parameters.Add(new SqlParameter()
{
ParameterName = "@RequiredButNullableUnicodeString",
DbType = System.Data.DbType.String,
Size = 50,
Value = DBNull.Value
});
parameters.Add(new SqlParameter()
{
ParameterName = "@SomeVarchar",
DbType = System.Data.DbType.AnsiString,
Size = 100,
Value = "some value"
});
[已解决](见尾)
我花了一天的大部分时间寻找一个没有运气的解决方案。
- EF 核心 3.1
- Visual Studio 2019
- SQL 服务器 2017
- .网络核心 3.1
我一直无法找到一种解决方案,可以使用可选参数从 .Net Core 3.1 调用存储过程。我已经阅读了从 SO 到博客的数十篇文章,但没有任何效果。
这是我的 C# 代码,用于调用存储过程的方法,并进行了所有尝试以实现此目的:
public List<BudgetWorkflowStatusByDepartment> GetBudgetWorkflowStatusByDepartment(int? companyId, int? departmentId, int? locationId, int? sublocationId)
{
//var compId = new SqlParameter("@CompanyId", companyId);
//compId.Value = (object)companyId ?? SqlInt32.Null;
//var deptId = new SqlParameter("@DepartmentId", departmentId);
//deptId.Value = (object)departmentId ?? SqlInt32.Null;
//var locId = new SqlParameter("@LocationId", locationId);
//locId.Value = (object)locationId ?? SqlInt32.Null;
//var subId = new SqlParameter("@SubLocationId", sublocationId);
//subId.Value = (object)sublocationId ?? SqlInt32.Null;
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment @CompanyID, @DepartmentID, @LocationID, @SubLocationID", compId, deptId, locId, subId).ToList();
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment {0}, {1}, {2}, {3}", companyId, departmentId, locationId, companyId).ToList();
// Error Must declare the scalar variable "@CompanyID".
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlRaw($"EXEC mis.BudgetWorkflowStatusByDepartment @CompanyID={compId}, @DepartmentID={deptId}, @LocationID={locId}, @SubLocationID={subId}").ToList();
// Error Must declare the scalar variable "@CompanyID".
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw($"EXEC mis.BudgetWorkflowStatusByDepartment {compId}, {deptId}, {locId}, {subId}").ToList();
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment @CompanyID={compId}, @DepartmentID={deptId}, @LocationID={locId}, @SubLocationID={subId}").ToList();
return result;
}
None 这些尝试都奏效了。错误范围为:
Data is Null. This method or property cannot be called on Null values.
...到...
Must declare the scalar variable "@compId".
...当我尝试一些很可能只是不正确的东西时,为了让它工作而绝望地尝试了一些其他错误。
这是存储过程本身的签名,表明所有参数都可以为空。
ALTER PROCEDURE [mis].[BudgetWorkflowStatusByDepartment]
(@CompanyID int = null,
@DepartmentID int = null,
@LocationID int = null,
@SubLocationID int = null)
令人沮丧的是,此存储过程在 SQL 服务器中,在带或不带参数的 LinqPad 中都有效。它在我的代码中仅适用于所有参数,但如果有任何参数为空,则会引发错误。
任何关于如何让函数使用可选参数的见解,我都非常感激。
编辑:2020 年 8 月 24 日
根据新建议,我尝试了它们,但出于某种原因它们仍然不起作用。
我已经在 SSMS 中使用所有四个参数直接测试了存储过程,它可以正常工作。然后我一个一个地删除了最后一个参数并在每个参数被删除时对其进行了测试并且它可以正常工作 returning 正确的数据。
当我使用 Api 和对此方法的服务调用进行相同的测试时,4 个参数有效,3 个参数有效,但 2 个参数、1 个参数和无参数均无效!我不明白为什么不。
这是我的新尝试:
/// New attempts as of: 8/24/2020
// Error Data is Null. This method or property cannot be called on Null values.
//var compId = new SqlParameter("@CompanyID", companyId);
//compId.Value = (object)companyId ?? DBNull.Value;
//var deptId = new SqlParameter("@DepartmentID", departmentId);
//deptId.Value = (object)departmentId ?? DBNull.Value;
//var locId = new SqlParameter("@LocationID", locationId);
//locId.Value = (object)locationId ?? DBNull.Value;
//var subId = new SqlParameter("@SubLocationID", sublocationId);
//subId.Value = (object)sublocationId ?? DBNull.Value;
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment {compId}, {deptId}, {locId}, {subId}").ToList();
// Error Data is Null. This method or property cannot be called on Null values.
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment {companyId}, {departmentId}, {locationId}, {sublocationId}").ToList();
//var compId = new SqlParameter("@CompanyID", companyId) { IsNullable = true };
//compId.Value = (object)companyId ?? DBNull.Value;
//var deptId = new SqlParameter("@DepartmentID", departmentId) { IsNullable = true };
//deptId.Value = (object)departmentId ?? DBNull.Value;
//var locId = new SqlParameter("@LocationID", locationId) { IsNullable = true };
//locId.Value = (object)locationId ?? DBNull.Value;
//var subId = new SqlParameter("@SubLocationID", sublocationId) { IsNullable = true };
//subId.Value = (object)sublocationId ?? DBNull.Value;
//// Error Data is Null. This method or property cannot be called on Null values.
//var result = context.BudgetWorkflowStatusByDepartment
// .FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment @CompanyID = @CompanyID, @DepartmentID = @DepartmentID, @LocationID = @LocationID, @SubLocationID = @SubLocationID", compId, deptId, locId, subId)
// .ToList();
var compId = new SqlParameter("@CompanyID", companyId) { IsNullable = true };
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId) { IsNullable = true };
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId) { IsNullable = true };
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId) { IsNullable = true };
subId.Value = (object)sublocationId ?? DBNull.Value;
// Error Data is Null. This method or property cannot be called on Null values.
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment @CompanyID, @DepartmentID, @LocationID, @SubLocationID", compId, deptId, locId, subId)
.ToList();
我想我应该将我的 DbContext 属性 设置包含在这个存储过程中作为附加信息。同样的模式用于另外两个存储过程,它们可以工作,但不需要可选参数。
public DbSet<BudgetWorkflowStatusByDepartment> BudgetWorkflowStatusByDepartment { get; set; }
并且在 OnModelCreating() 方法中我设置了 HasNoKey()
方法。
modelBuilder.Entity<BudgetWorkflowStatusByDepartment>().HasNoKey();
感谢大家的建议。
我的解决方法是告诉 DBA 更改存储过程以一起删除参数,我将只对结果做一个 .Where()
子句。
幸运的是,对于此特定功能,它不会 return 超过 200 行,但它会 return 许多列,但这仍然不应该很费力。
已解决原因
这是我的错。我有一些 int
属性需要是可为空的整数。一旦我这样做了,它就起作用了,而且它似乎适用于大多数解决方案。
重要的是要注意那些可能为 null 的属性并使它们可以为 null,这样您就不会成为我的问题的牺牲品,我花了一整天的时间才解决。
我想既然我正在尝试所有这些我以前没有做过的事情(使用可选参数和 .Net Core 3.1 存储过程),我会用各种选项记录我的结果处理这个。
这些是关于从 .Net Core 3.1 执行存储过程时有效和无效的一些选项。
对于这些示例,这将是 .Net Core 方法的签名。
public List<BudgetWorkflowStatusByDepartment> GetBudgetWorkflowStatusByDepartment
(int? companyId, int? departmentId, int? locationId, int? sublocationId)
{
...
}
所有参数都是可选的。以下示例将是您可以在此方法中使用的代码。
这是存储过程的签名。
ALTER procedure mis.BudgetWorkflowStatusByDepartment_Filtered (
@CompanyID int = null
, @DepartmentID int = null
, @LocationID int = null
, @SubLocationID int = null
)
工作示例
以下示例演示如何使用 FromSqlRaw
或 FromSqlInterpolated
方法来执行存储过程。
来自Sql原始方法
此示例仅创建 SqlParameter
个对象并将值设置为传入值或 DbNull
。这很重要,因为 null
在这种情况下不起作用。 (参见下面的非工作示例)
// Works
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID, @DepartmentID, @LocationID, @SubLocationID", compId, deptId, locId, subId).ToList();
以下通过包含 SqlParameter
对象的 IsNullable=true
属性 来实现。
// Works
var compId = new SqlParameter("@CompanyID", companyId) { IsNullable = true };
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId) { IsNullable = true };
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId) { IsNullable = true };
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId) { IsNullable = true };
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID = @CompanyID, @DepartmentID = @DepartmentID, @LocationID = @LocationID, @SubLocationID = @SubLocationID", compId, deptId, locId, subId)
.ToList();
下面和上面类似,只是稍微简写的语法方法。
// Works
var compId = new SqlParameter("@CompanyID", companyId) { IsNullable = true };
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId) { IsNullable = true };
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId) { IsNullable = true };
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId) { IsNullable = true };
subId.Value = (object)sublocationId ?? DBNull.Value;
// Works
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID, @DepartmentID, @LocationID, @SubLocationID", compId, deptId, locId, subId)
.ToList();
从Sql插值
以下有效,因为它使用 .FromSqlInterpolated()
方法,然后占位符语法将起作用。
// Works
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID={compId}, @DepartmentID={deptId}, @LocationID={locId}, @SubLocationID={subId}").ToList();
下面与上面类似,但它使用了稍微简写的语法。
// Works
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered {compId}, {deptId}, {locId}, {subId}").ToList();
以下是速记语法的终极。使用插值方法,您可以直接使用传入的变量,而不必创建 SqlParameter
个对象。
// Works
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered {companyId}, {departmentId}, {locationId}, {sublocationId}").ToList();
非工作示例
下面的代码return是一个空数组,这是不正确的。代码的这种参数化只能以这种方式与“插值”Sql 方法一起使用。
// Returns empty array
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment_Filtered {0}, {1}, {2}, {3}", companyId, departmentId, locationId, companyId).ToList();
结果 - 一个空数组,这是不正确的:
[]
下面的方法在FromSqlRaw
方法的语法中是不正确的。字符串中的参数想要使用插值,但这是该语法的错误方法。
// Error Must declare the scalar variable "@CompanyID".
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID={compId}, @DepartmentID={deptId}, @LocationID={locId}, @SubLocationID={subId}").ToList();
错误:
Must declare the scalar variable "@CompanyID".
以下也不正确。字符串中的参数不能以这种方式填充。
// Error Must declare the scalar variable "@CompanyID".
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered {compId}, {deptId}, {locId}, {subId}").ToList();
错误:
Must declare the scalar variable "@CompanyID".
最后,这也不起作用,因为 ??
运算符将空值设置为 null
而不是 DbNull.Value
。
var compId = new SqlParameter("@CompanyID", companyId);
compId.Value = (object)companyId ?? null;
var deptId = new SqlParameter("@DepartmentID", departmentId);
deptId.Value = (object)departmentId ?? null;
var locId = new SqlParameter("@LocationID", locationId);
locId.Value = (object)locationId ?? null;
var subId = new SqlParameter("@SubLocationID", sublocationId);
subId.Value = (object)sublocationId ?? null;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID, @DepartmentID, @LocationID, @SubLocationID", compId, deptId, locId, subId).ToList();
错误:
The parameterized query '(@CompanyID nvarchar(4000),@DepartmentID nvarchar(4000),@Locatio' expects the parameter '@CompanyID', which was not supplied.
我只是 re-created 一个类似的设置(所有你拥有的相同版本),以下 one-liner 对我有用:
public List<BudgetWorkflowStatusByDepartment> GetBudgetWorkflowStatusByDepartment(int? companyId, int? departmentId, int? locationId, int? sublocationId)
=> context.BudgetWorkflowStatusByDepartment
.FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment {companyId}, {departmentId}, {locationId}, {subLocationId}").ToList();
注意,因为我正在使用 .FromSqlInterpolated()
调用,所以不需要创建 SqlParameter
s - int?
可以直接传入,因为这是一个安全的根据 docs 调用,null
值得到正确处理。
我看到你也试过这个调用,但看起来你传递的是 SqlParameter
s,而不是数据本身 - 试试我的版本?
经过进一步测试,我让你的版本可以工作,稍作修改,并将 SqlParameter.IsNullable
属性 设置为 true
:
public List<BudgetWorkflowStatusByDepartment> GetBudgetWorkflowStatusByDepartment(int? companyId, int? departmentId, int? locationId, int? sublocationId)
{
var compId = new SqlParameter("@CompanyID", companyId) { IsNullable = true };
compId.Value = (object)companyId ?? DBNull.Value;
var deptId = new SqlParameter("@DepartmentID", departmentId) { IsNullable = true };
deptId.Value = (object)departmentId ?? DBNull.Value;
var locId = new SqlParameter("@LocationID", locationId) { IsNullable = true };
locId.Value = (object)locationId ?? DBNull.Value;
var subId = new SqlParameter("@SubLocationID", sublocationId) { IsNullable = true };
subId.Value = (object)sublocationId ?? DBNull.Value;
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment @CompanyID = @CompanyID, @DepartmentID = @DepartmentID, @LocationID = @LocationID, @SubLocationID = @SubLocationID", compId, deptId, locId, subId)
.ToList();
return result;
}
任你选择 - 我个人喜欢 one-liner :)
我发现使用 dotnetcore 获取命名参数(以及可选参数)的唯一方法是使用以下方法:
_context.Database.ExecuteSqlRaw("EXEC mysproc @param1={0}, @param2={1}"
,param1
,param2);
... 其中 param1 和 param2 是 Microsoft.Data.SqlClient.SqlParameter 类型(这很重要,因为我发现 System.Data.SqlClient.SqlParamater 类型不会产生编译错误但会导致令人困惑的异常运行次)。
虽然这始终有效,即使使用输出参数(在参数占位符后添加“out”),当您的代码可能使用不同的参数集提供不同的结果时,这是一种非常不灵活的创建语句的方式.我的一个用例有 23 个我可能会提供的参数,其中只有三个是必需的。硬编码 ExecuteSqlRaw 语句不是一种选择。
从那以后,我想出了一个有用的方法,我在我的控制器、页面等中使用了注入 DbContext _context 的地方:
private void ExecuteStoredProcedure(
string StoredProcedureName
,List<SqlParameter> parameters)
{
StringBuilder stringBuilder = null;
int paramNum = 0;
// Build the T-SQL statement to enable named paramaters
foreach (SqlParameter param in parameters)
{
string direction = param.Direction == ParameterDirection.Output ? " out" : "";
if (stringBuilder == null)
{
stringBuilder = new StringBuilder(string.Format(CultureInfo.CurrentCulture
, "EXEC {0} {1}={{{2}}}{3}"
, StoredProcedureName
, param.ParameterName
, paramNum++
, direction));
}
else
{
stringBuilder.Append(string.Format(CultureInfo.CurrentCulture
, ",{0}={{{1}}}{2}"
, param.ParameterName
, paramNum++
, direction));
}
}
try
{
_context.Database.ExecuteSqlRaw(
stringBuilder.ToString()
,parameters.ToArray());
}
catch (Exception ex)
{
throw;
}
}
该方法采用任意顺序的 SqlParameters 列表。请注意,列表在传递给 ExecuteSqlRaw 之前会转换为 SqlParamater 数组。它在技术上不是一个“参数数组”,但我还没有看到它失败。您可能还想将 DBContext 或 DatabaseFacade 传递给该方法。
我通常按以下方式构造参数列表。 ParameterName 属性 必须匹配它将提供的存储过程参数的名称。我排除了 inclusion/exclusion 个参数的任何逻辑。
List<SqlParameter> parameters = new List<SqlParameter>();
parameters.Add(new SqlParameter()
{
ParameterName = "@MyOutputParam",
DbType = System.Data.DbType.Int64,
Direction = System.Data.ParameterDirection.Output
});
parameters.Add(new SqlParameter()
{
ParameterName = "@RequiredButNullableUnicodeString",
DbType = System.Data.DbType.String,
Size = 50,
Value = DBNull.Value
});
parameters.Add(new SqlParameter()
{
ParameterName = "@SomeVarchar",
DbType = System.Data.DbType.AnsiString,
Size = 100,
Value = "some value"
});