13
votes

When I execute a SQL Server stored procedure from Entity Framework Core (v2.0) in my ASP.NET Core project, I get this exception:

InvalidOperationException: no mapping to a relational type can be found for the CLR type 'Int32[]'

The SQL Server stored procedure code looks like this:

CREATE PROCEDURE [dbo].[sp-UpdateProductOrderAndStock]
    @customerOrderID INT,
    @qty INT
AS
    DECLARE @customerProductID INT
    SET @customerProductID = (SELECT CustomerProductID 
                              FROM dbo.CustomerOrder 
                              WHERE ID = @customerOrderID)

    SET NOCOUNT ON;  

    UPDATE dbo.CustomerOrder
    SET CompletedQty = CompletedQty + @qty
    WHERE ID = @customerOrderID;

    UPDATE dbo.CProductStation
    SET Qty = Qty - @qty
    WHERE CustomerProductID = @customerProductID AND StationNo = 0;

ASP.NET Core data model as below:

public class CustomerProductDelivery
{
    public int ID { get; set; }
    public int CustomerOrderID { get; set; }
    public DateTime DeliveryDate { get; set; }
    public string DeliveryNo { get; set; }
    public int DeliveryQty { get; set; }
    public CustomerOrder CustomerOrder { get; set; }
}

ASP.net core Controller code to execute the stored procedure:

_context.Database.ExecuteSqlCommand("sp-UpdateProductOrderAndStock @p0, @p1", parameters: new[] { customerProductDelivery.CustomerOrderID,customerProductDelivery.DeliveryQty });
3
The stored procedure is called [dbo].[Update_ProductOrderAndStock] - but you're calling a procedure called sp-UpdateProductOrderAndStock from EF Core ..... those names must match !marc_s

3 Answers

21
votes

TLDR

Use string interpolation

_context.Database.ExecuteSqlCommand($"Update_ProductOrderAndStock {customerProductDelivery.CustomerOrderID}, {customerProductDelivery.DeliveryQty}");

Detailed answer:

You are falling into a typical params object[] trap.

First, let see what's the type of the expression

var parameters = new[] { customerProductDelivery.CustomerOrderID,customerProductDelivery.DeliveryQty }

Since both elements are of type int, the inferred type is int[].

Now, there are 2 ExecuteSqlCommand method overloads which allow you to pass parameters separately - one receiving params object[] and second receiving IEnumerable<object>.

Although IEnumerable<T> is covariant, covariance does not work for value types, so IEnumerable<int> cannot be treated as IEnumerable<object>, hence the second overload does not match.

So the only valid overload is the one receiving params object[]. But since int[] cannot be cast to object[], the compiler treats it as single object, thus emitting something like new object[] { parameters }.

The net effect is that the EF method receives single parameter with int[] type value and generates the exception in question.

With that being said, it could be fixed in many ways.

One is to remove new [] { } from the call, which is the whole idea of params object[] construct:

_context.Database.ExecuteSqlCommand(
    "Update_ProductOrderAndStock @p0, @p1", customerProductDelivery.CustomerOrderID, customerProductDelivery.DeliveryQty);

Another is to replace the new [] with the explicit new object[].

Yet another one is to utilize the EF Core introduced String interpolation in FromSql and ExecuteSqlCommand:

_context.Database.ExecuteSqlCommand(
    $"Update_ProductOrderAndStock {customerProductDelivery.CustomerOrderID}, {customerProductDelivery.DeliveryQty}");
7
votes

Please update the code

from

    _context.Database.ExecuteSqlCommand(
                   "sp_UpdateProductOrderAndStock @p0, @p1", parameters: 
                     new[] 
                     { 
                        customerProductDelivery.CustomerOrderID,customerProductDelivery.DeliveryQty
                      });

to

List<SqlParameter> pc = new List<SqlParameter>
{
   new SqlParameter("@customerOrderID", customerProductDelivery.CustomerOrderI),
   new SqlParameter("@qty", customerProductDelivery.DeliveryQty)
}

_context.Database.ExecuteSqlCommand("sp_UpdateProductOrderAndStock @customerOrderID, @qty", pc.ToArray());
0
votes

Another Answer

var p0 = new SqlParameter("@customerOrderID", customerProductDelivery.CustomerOrderID);
        var p1 = new SqlParameter("@qty", customerProductDelivery.DeliveryQty);
        _context.Database.ExecuteSqlCommand("sp_UpdateProductOrderAndStock @customerOrderID, @qty", p0, p1);

        //or  var p0 = new SqlParameter("@p0", customerProductDelivery.CustomerOrderID);
        //or  var p1 = new SqlParameter("@p1", customerProductDelivery.DeliveryQty);
        // or _context.Database.ExecuteSqlCommand("sp_UpdateProductOrderAndStock @p0, @p1", p0, p1);