Brendan Vogt
Brendan Vogt

Reputation: 26048

FunctionImport in entity framework 4 issue

I'm using entity framework 4.

I have a stored procedure that just updates one value in my table, namely the application state ID. So I created a stored procedure that looks like this:

ALTER PROCEDURE [dbo].[UpdateApplicationState]
(
   @ApplicationID INT,
   @ApplicationStateID INT
)

AS

BEGIN

   UPDATE
      [Application]
   SET
      ApplicationStateID = @ApplicationStateID
   WHERE
      ApplicationID = @ApplicationID;

END

I created a function import called UpdateApplicationState. I had initially set its return type to null, but then it wasn't created in the context. So I changed its return type to int. Now it was created in the context. Is it wise to return something from my stored procedure?

Here is my method in my ApplicationRepository class:

public void UpdateApplicationState(int applicationID, int applicationStateID)
{
   var result = context.UpdateApplicationState(applicationID, applicationStateID);
}

Here is my calling code to this method in my view:

applicationRepository.UpdateApplicationState(id, newApplicationStateID);

When I run it then I get the following error:

The data reader returned by the store data provider does not have enough columns for the query requested.

Any idea/advise on what I can do to get this to work?

Thanks

Upvotes: 9

Views: 7331

Answers (2)

Johnny Oshika
Johnny Oshika

Reputation: 57552

To get POCO to work with function imports that return null, you can customize the .Context.tt file like this.

Find the "Function Imports" named region (the section that starts with region.Begin("Function Imports"); and ends with region.End();) in the .Context.tt file and replace that whole section with the following:

    region.Begin("Function Imports");

        foreach (EdmFunction edmFunction in container.FunctionImports)
        {
            var parameters = FunctionImportParameter.Create(edmFunction.Parameters, code, ef);
            string paramList = String.Join(", ", parameters.Select(p => p.FunctionParameterType + " " + p.FunctionParameterName).ToArray());
            var isReturnTypeVoid = edmFunction.ReturnParameter == null;
            string returnTypeElement = String.Empty;
            if (!isReturnTypeVoid) 
                returnTypeElement = code.Escape(ef.GetElementType(edmFunction.ReturnParameter.TypeUsage));

#>
<# if (isReturnTypeVoid) { #>
    <#=Accessibility.ForMethod(edmFunction)#> void <#=code.Escape(edmFunction)#>(<#=paramList#>)    
<# } else { #>
    <#=Accessibility.ForMethod(edmFunction)#> ObjectResult<<#=returnTypeElement#>> <#=code.Escape(edmFunction)#>(<#=paramList#>)    
<# } #>
    {
<#
            foreach (var parameter in parameters)
            {
                if (!parameter.NeedsLocalVariable)
                {
                    continue;
                }
#>

        ObjectParameter <#=parameter.LocalVariableName#>;

        if (<#=parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null"#>)
        {
            <#=parameter.LocalVariableName#> = new ObjectParameter("<#=parameter.EsqlParameterName#>", <#=parameter.FunctionParameterName#>);
        }
        else
        {
            <#=parameter.LocalVariableName#> = new ObjectParameter("<#=parameter.EsqlParameterName#>", typeof(<#=parameter.RawClrTypeName#>));
        }
<#
            }
#>
<# if (isReturnTypeVoid) { #>
        base.ExecuteFunction("<#=edmFunction.Name#>"<#=code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()))#>);
<# } else { #>
        return base.ExecuteFunction<<#=returnTypeElement#>>("<#=edmFunction.Name#>"<#=code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()))#>);
<# } #>

    }
<#
        }

        region.End();

What I'm doing here is instead of ignoring all function imports that return null, I'm creating a method that returns null. I hope this is helpful.

Upvotes: 20

Morteza Manavi
Morteza Manavi

Reputation: 33216

It is because you do not actually returning anything from your stored procedure. Add a line like below to your SP (SELECT @@ROWCOUNT), and it will be executing properly.

BEGIN    
    ...

    SELECT @@ROWCOUNT
END

While this solution will address your issue and actually returns the number of effected rows by your SP, I am not clear on why this is an issue for you:

I had initially set its return type to null, but then it wasn't created in the context.

When doing a Function Import, you can select "None" as return type and it will generate a new method on your ObjectContext with a return type of int. This method basically executes a stored procedure that is defined in the data source; discards any results returned from the function; and returns the number of rows affected by the execution.

EDIT: Why a Function without return value is ignored in a POCO Scenario:

Drilling into ObjectContext T4 template file coming with ADO.NET C# POCO Entity Generator reveals why you cannot see your Function in your ObjectContext class: Simply it's ignored! They escape to the next iteration in the foreach loop that generates the functions.

The workaround for this is to change the T4 template to actually generate a method for Functions without return type or just returning something based on the first solution.

region.Begin("Function Imports");

foreach (EdmFunction edmFunction in container.FunctionImports)
{
    var parameters = FunctionImportParameter.Create(edmFunction.Parameters, code, ef);
    string paramList = String.Join(", ", parameters.Select(p => p.FunctionParameterType + " " + p.FunctionParameterName).ToArray());

    // Here is why a Function without return value is ignored:
    if (edmFunction.ReturnParameter == null)
    {
        continue;
    }
    string returnTypeElement = code.Escape(ef.GetElementType(edmFunction.ReturnParameter.TypeUsage));
    ...

Upvotes: 16

Related Questions