【发布时间】:2020-12-11 07:55:19
【问题描述】:
[已解决](见结尾)
我大部分时间都在寻找一个没有运气的解决方案。
- EF Core 3.1
- Visual Studio 2019
- SQL Server 2017
- .Net Core 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;
}
这些尝试都不起作用。错误范围为:
数据为空。不能对 Null 值调用此方法或属性。
...到...
必须声明标量变量“@compId”。
...当我刚刚尝试了一些很可能只是不正确的东西以拼命尝试让它工作时出现的一些其他错误。
这是存储过程本身的签名,表明所有参数都可以为空。
ALTER PROCEDURE [mis].[BudgetWorkflowStatusByDepartment]
(@CompanyID int = null,
@DepartmentID int = null,
@LocationID int = null,
@SubLocationID int = null)
令人沮丧的是,这个存储过程在 SQL Server 和 LinqPad 中无论有无参数都可以工作。它在我的代码中仅适用于所有参数,但如果有任何参数为空,则会引发错误。
感谢任何有关如何让函数使用可选参数的见解。
编辑:2020 年 8 月 24 日
根据新的建议,我尝试了它们,但由于某种原因它们仍然不起作用。
我已经在 SSMS 中使用所有四个参数直接测试了存储过程,并且它可以工作。然后我一个一个地删除了最后的每个参数,并在每个参数都被删除时对其进行了测试,它可以返回正确的数据。
当我使用 Api 和对该方法的服务调用进行相同的测试时,四个参数有效,三个参数有效,但是 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() 子句。
幸运的是,对于这个特殊的功能,它不会返回超过 200 行,但它会返回很多列,但这仍然不应该是繁重的。
解决原因
这是我的错。我有一些int 属性需要为可空整数。一旦我这样做了,它就起作用了,而且它似乎适用于大多数解决方案。
重要的是要注意那些可能为空的属性并使它们可以为空,这样你就不会成为我的问题的牺牲品,这需要一整天的时间来解决。
我认为,由于我正在经历所有这些我以前没有做过的尝试(使用带有 .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 方法来执行存储过程。
FromSqlRaw 方法
此示例仅创建 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();
FromSqlInterpolated
以下内容有效,因为它使用.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();
非工作示例
以下代码返回一个空数组,这是不正确的。代码的这种参数化只能与“插值”的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.
【问题讨论】:
-
这能回答你的问题吗? Optional Parameters with EF Core FromSql
-
@CoolBots 不幸的是没有。我其实也试过了。我会继续寻找。
-
我刚刚在您的注释代码中看到了这一点 - 我建议通过您帖子中的 EDIT 将其放在前面和中心 - 否则,您将被标记为重复。你可以试试 SQL Profiler,看看会发生什么,看看如果你在 SSMS 中执行那个确切的命令会出现什么错误?
-
我还要仔细检查一下,以防万一,命名参数似乎是可选参数存储过程执行的要求。您注释代码中的格式与类似问题中的格式有点不同 - 一个是插值字符串 - 试试看?
-
对不起,把什么放在前面和中间?我也确实使用了 SQL Profiler,它返回了这个:
exec sp_executesql N'EXEC mis.BudgetWorkflowStatusByDepartment @CompanyID=@CompanyID, @DepartmentID=@DepartmentID, @LocationID=@LocationID, @SubLocationID=@SubLocationID ',N'@CompanyID nvarchar(4000),@DepartmentID nvarchar(4000),@LocationID nvarchar(4000),@SubLocationID nvarchar(4000)',@CompanyID=NULL,@DepartmentID=NULL,@LocationID=NULL,@SubLocationID=NULL。不知道为什么 nvarchar(4000) 在那里,但它是。
标签: c# sql-server stored-procedures entity-framework-core optional-parameters