Entity Framework Code First and Stored Procedures

4.48 (23 votes)

A key reason behind the decision to use an ORM such as Entity Framework for your data access is to reduce or eliminate the need to write SQL as part of the development process. Nevertheless, there are times when you might need to make use of database stored procedures, and Entity Framework 6 Code First provides support for using existing stored procedures as well as generating new ones.

There are a number of reasons why you might need to call stored procedures when working with the Entity Framework. It might be that you are working with a legacy database where all access has been locked down to stored procedures, or you might have to resort to procedures for particularly complex problems where the generated SQL from the Entity Framework is just not up to the task. You could resort to plain ADO.NET for these occasions, but you lose the ability to work with data in a strongly typed way unless you want to add a lot of plumbing code to hydrate business objects from DataReaders.

Selecting entities

The data that you select will sometimes map directly to the entities that form part of your domain model. You can use the DbSet.SqlQuery method to execute the stored procedure, which will result in the retrieved entities being tracked by the context (unless you turn tracking off). The following method uses a stored procedure in the Northwind database to retrieve an Employee instance:

using (var context = new NorthWindContext())
{
    SqlParameter param1 = new SqlParameter("@EmployeeID", 6); 
    var employee = await context.Employees.SqlQuery("GetEmployee @EmployeeID", param1).SingleAsync();
}

The stored procedure includes a named parameter which is created and passed as the second argument to the SqlQuery method. Some existing documentation suggests that you can simply pass the value of the parameter as the second argument, but doing so will result in a SqlException telling you that you must declare the scalar variable xxx. Notice the use of SingleAsync (or just Single) if you aren't working asynchronously. This forces execution of the query, which results in the employee variable representing an instance of the Employee type. The following code (without the Single method being used) will result in  a compiler error:

Employee employee = context.Employees.SqlQuery("GetEmployee @EmployeeID", param1);

The error message will be along the lines of "Cannot implicitly convert type 'System.Data.Entity.Infrastructure.DbSqlQuery<EFStoredProcedures.Models.Employee>' to 'EFStoredProcedures.Models.Employee'". Just like a LINQ query, until you force execution of the SqlQuery method through the use of First(), Single(), ToList() etc, the right hand side of the expression represents an instance of the actual query object, not the result of executing it.

The Single method will work with stored procedures that return a single result. It will result in an error if more than one record is returned. If your stored procedure returns multiple records, you use one of the extension methods that caters of collections: ToList(), ToArray() etc:

List<Employee> employees = await context.Employees.SqlQuery("GetAllEmployees").ToListAsync();

Selecting non-entity types

Sometimes the stored procedures will return data that doesn't map to existing entities. It might return only a subset of the data required for an entity or it might return a scalar value. In these cases there is no DbSet to call the SqlQuery method on, so you call the version of the SqlQuery method found on the Database property of the DbContext class instead, which is a generic method. The next example illustrates how to call a stored procedure that returns a scalar value. Here's the procedure:

CREATE procedure [dbo].[CountOfOrders]
    @ProductId int
AS
SELECT Count(*) From Orders o 
INNER JOIN OrderDetails od ON od.OrderID = o.OrderID 
WHERE od.ProductID = @ProductId

And here's how it is called in code:

SqlParameter param1 = new SqlParameter("@ProductID", 72); 
var totalOrders = await context.Database.SqlQuery<int>("CountOfOrders @ProductID", param1).SingleAsync();

The Database.SqlQuery method takes a type argument representing how the result of the stored procedure should be mapped to .NET code. The CountOfOrders procedure returns a numeric, which is mapped to a C# int. If you want to return complex types, you need to create a class to represent them. The following procedure returns data with the following fields: FirstName (string), LastName (string) and TotalOrders (numeric):

CREATE procedure [dbo].[AllSalesPeople] 
AS
SELECT  FirstName, LastName, SUM(os.Subtotal) AS TotalSales
FROM Employees e INNER JOIN
    (Orders o INNER JOIN OrderSubtotals os ON o.OrderID = os.OrderID) 
    ON e.EmployeeID = o.EmployeeID 
    GROUP BY e.LastName, e.FirstName 
    ORDER BY TotalSales DESC

This needs to be mapped to a class:

public class SalesPerson
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public decimal TotalSales { get; set; }
}

You call this procedure by passing SalePerson as the type parameter to the SqlQuery method:

var salesPeople = await context.Database.SqlQuery<SalesPerson>("AllSalesPeople").ToListAsync();

Executing non-query procedures

The Database class has another method for executing procedures that don't return results (INSERT, UPDATE, DELETE), the ExecuteSqlCommand method and its Async twin. The following example demonstrates its use in executing a procedure that adds a new category to the database:

using(var context = new NorthWindContext())
{
    SqlParameter category = new SqlParameter("@CategoryName", "Test");
    await context.Database.ExecuteSqlCommandAsync("NewCategory @CategoryName", category);
}

Parameters are buit in the same way as with the procedures that return results. While the example above doesn't show it, the ExecuteSqlCommand and ExecuteSqlCommandAsync methods return an int representing the number of rows of data affected by the execution of the procedure/query.

Generating stored procedures

Entity Framework can be used to generate simple non-query procedures for entities if you prefer to use those rather than the DbSet's Add and Remove methods. You enable this feature as part of the migration configuration by using the MapToStoredProcedures method. You can do this in the onModelCreating method:

modelBuilder.Entity<Territory>().MapToStoredProcedures();

Or you might prefer to keep your configuration in classes that derive from EntityTypeConfiguration<TEntity>:

public class TerritoryMap : EntityTypeConfiguration<Territory>
{
    public TerritoryMap()
    {
        MapToStoredProcedures();
    }
}

Either way, when you add a new migration, the Up method will include code to generate Create, Update and Delete procedures using the pattern EntityName_Action as a naming convention:

public override void Up()
{
    CreateStoredProcedure(
        "dbo.Territory_Insert",
        p => new
            {
                TerritoryID = p.String(maxLength: 20),
                TerritoryDescription = p.String(maxLength: 50),
                RegionID = p.Int(),
            },
        body:
            @"INSERT [dbo].[Territories]([TerritoryID], [TerritoryDescription], [RegionID])
                VALUES (@TerritoryID, @TerritoryDescription, @RegionID)"
    );
            
    CreateStoredProcedure(
        "dbo.Territory_Update",
        p => new
            {
                TerritoryID = p.String(maxLength: 20),
                TerritoryDescription = p.String(maxLength: 50),
                RegionID = p.Int(),
            },
        body:
            @"UPDATE [dbo].[Territories]
                SET [TerritoryDescription] = @TerritoryDescription, [RegionID] = @RegionID
                WHERE ([TerritoryID] = @TerritoryID)"
    );
            
    CreateStoredProcedure(
        "dbo.Territory_Delete",
        p => new
            {
                TerritoryID = p.String(maxLength: 20),
            },
        body:
            @"DELETE [dbo].[Territories]
                WHERE ([TerritoryID] = @TerritoryID)"
    );
            
}

You are able to modify the code in the Up method to change the names of the stored procedures if you wish.

Entity Framework Core Support

Entity Framework Core (previously known as Entity Framework 7) is a new version of EF designed for use with the new ASP.NET Core framework, which is intended for cross-platform development. The RTM version of EF Core should be available very soon (end June 2016) but will only include support for calling stored procedures that return entities. These will be invoked by calling a new DbSet method - FromSql e.g

using (var context = new NorthWindContext())
{
    var products = await context.Products.FromSql("TenMostExpensiveProducts").ToListAsync();
}

Summary

When I was looking for information on supporting stored procedures with Entity Framework, most of the articles I found related to Database First and edmx files, or they only covered one aspect of stored procedure support, mostly selecting entities. I put this article together to cover all of the most common scenarios related to using stored procedures with EF 6 Code First and migrations. As a result, this article has explored mapping results to entities and non-entities, returning collections, single results and scalar values. It has also shown how to execute procedures that don't return data, and how to use migrations to generate simple CUD procedures for entities. Finally, it briefly looked at the current level of support offered in the new version of Entity Framework.

You might also like...

Date Posted:
Last Updated:
Posted by:
Total Views to date: 40607

3 Comments

- satyabrata

Nice article. Thanks.
The menu on this website not working in mobile.

- Jose Negro

Thank you very much for so clear examples.

- Steven

Can you provide the directives (using statements) you're using for EF7 example?

Recent Comments

Cyrus 26/05/2017 06:00
In response to Razor Pages - The Elevator Pitch
There are some concern about razor pages performance. Is it faster or slower than MVC? would you a a...

Cyrus 26/05/2017 05:44
In response to Razor Pages - Understanding Handler Methods
well done, thank you....

Satyabrata Mohapatra 23/05/2017 11:41
In response to Razor Pages - Understanding Handler Methods
Nice and easy !! Great post....

Cyrus 16/05/2017 19:55
In response to Razor Pages - Getting Started With The Preview
There is something wrong related to microsoft.dotnetcore.mvc.taghelpers! if you remove it from page...

Cyrus 16/05/2017 10:18
In response to Razor Pages - Getting Started With The Preview
well done mike, it was very useful, I really appreciate that....

Satyabrata Mohapatra 16/05/2017 07:21
In response to Razor Pages - Getting Started With The Preview
Finally!!!! web pages in asp.net core!!! Super excited !!!! Thank u sir for sharing.....Awaiting on...

Daniele 14/03/2017 10:24
In response to Working With Zip Files In ASP.NET MVC
is it possible give to the user a progress bar of the zipping process? Thanks in advance. ...

Suraj 13/03/2017 22:20
In response to Working With Zip Files In ASP.NET MVC
Very nice article. Thanks....

Satyabrata Mohapatra 19/02/2017 03:01
In response to Free SSL Certificates On IIS With LetsEncrypt
Thanks for sharing. Learned a lot !!...

Gfw 03/02/2017 09:48
In response to Free SSL Certificates On IIS With LetsEncrypt
I have used WinSimple for about the last 9 months - works great. One thing that you want to make of...