实体框架核心3.1存储过程可选参数不起作用 编辑:2020年8月24日已解决的原因 FromSqlRaw方法 FromSqlInterpolated

问题描述

[已解决](请参阅结尾)

我一天中的大部分时间都在寻找一种没有运气的解决方案。

  • 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},@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中使用。而且它仅在使用所有参数的情况下在我的代码中起作用,但是如果任何参数为null,则将引发错误。

对我如何使功能与可选参数一起使用的任何见解,均表示赞赏。

编辑: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},{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",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,subId)
                                .ToList();

我认为我应该将此存储的DbContext属性设置作为附加信息。相同的模式用于其他两个sproc,它们可以工作,但是不需要可选参数。

        public DbSet<BudgetWorkflowStatusByDepartment> BudgetWorkflowStatusByDepartment { get; set; }

然后在OnModelCreating()方法中设置HasNoKey()方法。

            modelBuilder.Entity<BudgetWorkflowStatusByDepartment>().HasNoKey();

我感谢每个人的建议。

我对此的解决方法是告诉DBA更改存储过程以一起删除所有参数,而我将对结果做一个.Where()子句。

幸运的是,对于此特定功能,它不会返回超过200行,但是它将返回许多列,但这仍然不应该加重负担。

已解决的原因

这是我的错。我有一些int属性,这些属性需要为可为null的整数。一旦这样做,它就可以工作,并且似乎适用于大多数解决方案。

重要的是要注意那些可能为null的属性并使它们可为空,这样您就不必费心整天来解决这个问题了。


我认为,由于我经历了所有以前从未尝试过的事情(使用.Net Core 3.1存储过程使用可选参数),因此我将在处理此问题时使用各种选项记录我的结果

从.Net Core 3.1执行存储过程时,这些是一些选项,哪些有效,哪些无效。

对于这些示例,这将是.Net Core方法的签名。

public List<BudgetWorkflowStatusByDepartment> GetBudgetWorkflowStatusByDepartment
(int? companyId,int? sublocationId)
{
...
}

所有参数都是可选的。以下示例将是您可以在此方法中使用的代码。

这是存储过程的签名。

ALTER procedure mis.BudgetWorkflowStatusByDepartment_Filtered (
      @CompanyID int = null,@SubLocationID int = null
)

工作示例

以下示例演示了如何使用FromSqlRawFromSqlInterpolated方法执行存储过程。

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,subId).ToList();


下面的工作通过包含IsNullable=true对象的SqlParameter属性来实现。

// 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,subId)
        .ToList();

以下与上面类似,但语法略为简化。

// Works
var compId = new SqlParameter("@CompanyID",sublocationId) { IsNullable = true };
subId.Value = (object)sublocationId ?? DBNull.Value;

// Works
var result = context.BudgetWorkflowStatusByDepartment
        .FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID,subId)
        .ToList();

FromSqlInterpolated

以下方法有效,因为它使用.FromSqlInterpolated()方法,然后占位符语法将起作用。

// Works
var compId = new SqlParameter("@CompanyID",sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;

var result = context.BudgetWorkflowStatusByDepartment
    .FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID={compId},@SubLocationID={subId}").ToList();

以下内容与上面类似,但使用的语法略为简化。

// Works
var compId = new SqlParameter("@CompanyID",sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;

var result = context.BudgetWorkflowStatusByDepartment
    .FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered {compId},{subId}").ToList();

以下是简写语法的最终版本。使用插值方法,您可以直接使用传入的变量,而不必创建SqlParameter对象。

// Works
var result = context.BudgetWorkflowStatusByDepartment
    .FromSqlInterpolated($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered {companyId},{sublocationId}").ToList();

非工作示例

以下代码返回一个空数组,这是不正确的。这种代码参数化只能通过“内插” Sql方法以这种方式使用。


// Returns empty array
var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw("EXEC mis.BudgetWorkflowStatusByDepartment_Filtered {0},companyId).ToList();

结果-一个空数组,这是不正确的:

[]

以下方法在FromSqlRaw方法的语法中不正确。字符串中的参数要使用插值,但这是该语法的错误方法。

// Error Must declare the scalar variable "@CompanyID".
var compId = new SqlParameter("@CompanyID",sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;

var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered @CompanyID={compId},@SubLocationID={subId}").ToList();

错误:

Must declare the scalar variable "@CompanyID".

以下内容也不正确。不能以此方式填充字符串中的参数。

// Error Must declare the scalar variable "@CompanyID".
var compId = new SqlParameter("@CompanyID",sublocationId);
subId.Value = (object)sublocationId ?? DBNull.Value;

var result = context.BudgetWorkflowStatusByDepartment
.FromSqlRaw($"EXEC mis.BudgetWorkflowStatusByDepartment_Filtered {compId},{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,subId).ToList();

错误:

The parameterized query '(@CompanyID nvarchar(4000),@DepartmentID nvarchar(4000),@Locatio' expects the parameter '@CompanyID',which was not supplied.

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)