December 2017 : Online courses in ASP.NET MVC and Angular 4. Conducted by Bipin Joshi. Read more...
Registration for December 2017 batches of ASP.NET MVC / Core and Angular 4 online courses have already started. Conducted by Bipin Joshi. Book your seat today ! Click here for more details.

Working with Stored Procedures in Entity Framework - Part 2

In Part 1 of this two part series, you learned to map stored procedures for the INSERT, UPDATE and DELETE operations in the entity framework designer. EF also allows you call arbitrary stored procedures from your code. These stored procedures might be doing some custom processing and there are at least four distinct types of values they can return back to the calling code:

  • Scalar values such as integer or string
  • Zero or more records that map exactly with some entity class
  • Zero or more records that return data not mapping directly to any existing entity class
  • Multiple result sets

Whatever be the case a basic thing you need to do is to import the required stored procedures in the EF model under consideration. In the examples illustrated in this article you will need to import the following stored procedures residing in the Northwind database. Some of these stored procedures are installed as a part of the Northwind database. Additionally you need to create a few on your own. The actual stored procedures and they return values are discussed in more details in the later sections. The following figure shows the "Update Model from Database" dialog of the EF designer:

Once you import the stored procedures in the model, they will be listed under the Function Imports folder inside the model browser (see below).

Next, double click on any of the stored procedure. Doing so will open the following dialog:

This dialog is important because this is where you can change the return type of a stored procedure. The possible return types are:

  • None
  • Scalars
  • Complex
  • Entities

The return type of none means stored procedure doesn't return anything. The return type of scalars indicates that the stored procedure under consideration returns a scalar value and its data type can be selected from the dropdown. Most of  the times EF designer automatically detects the data type for you. The return type of complex indicates that the stored procedure is returning records that do not map with any existing entity structure. These records are, therefore, mapped with custom complex types (classes) in .NET. The complex type is generated automatically by EF (more on that later) but if you wish you can also create and map a complex type on your own. The mapping can be changed through the dropdown in front of Complex radio button. The return types of Entities indicates that the stored procedure is returning records that are directly mapping with some existing entity. The name of the entity can be changed from the associated dropdown.

In many cases you won't need to even open this dialog because EF automatically sets the return type for each stored procedure you import. However, in some cases (as you will see it later) you may need to use it to modify the return type mapping. Once the return types of a stored procedure is set you are ready to use it in your code.

Now, that you have the basic idea about calling arbitrary stored procedures let's see one example of each return type (except the return type of None).

Calling a stored procedure that returns a scalar value

In this section you will call a stored procedure that returns an integer (i.e. scalar) value. The stored procedure that you will use is shown below:

CREATE PROCEDURE CustOrderCount
	@CustomerID nchar(5)
AS
BEGIN
	SELECT COUNT(*) FROM ORDERS 
        WHERE CUSTOMERID=@CustomerID;
END

The CustOrderCount stored procedure accepts a CustomerID and returns the number of orders belonging to that customer.

To call this procedure from C# code, first of all import it in your model as outlined at the beginning of this article. Then you can write the following C# code to call it:

 NorthwindEntities db = new NorthwindEntities();
var count = db.CustOrderCount("ALFKI");
int ordercount = count.SingleOrDefault().Value;

The above code creates a data context and then invokes the CustOrderCount() method on it. Notice that importing a stored procedure adds a method to the context class with the same name and parameters as the stored procedure. You can then call that method in your code.

The CustOrderCount() method returns an object of type ObjectResult. See the following signature of the CustOrderCount() method as shown in VS:

The ObjectResult class represents an enumerable results returned by the stored procedure. So, to get the actual value as returned by the stored procedure you need to use SingleOrDefault() method and then use the Value property. If you run this code you should get 6 as the ordercount for CustomerID of ALFKI.

Calling a stored procedure that returns records that do not map with any entity

In this section you will call a stored procedure that returns order history of a customer. The stored procedure selects ProductName and its total Quantity using the following query:

CREATE PROCEDURE CustOrderHist 
  @CustomerID nchar(5)
AS
  SELECT ProductName, Total=SUM(Quantity)
  FROM Products P, [Order Details] OD, Orders O, Customers C
  WHERE C.CustomerID = @CustomerID
  AND C.CustomerID = O.CustomerID AND O.OrderID = OD.OrderID 
  AND OD.ProductID = P.ProductID
  GROUP BY ProductName

The CustOrderHist stored procedure is available with the default installation of Northwind database. Before you call this this stored procedure import it in the model as before. This time the EF designer will also create a CustOrderHist_Result complex type for you. You can confirm this by double clicking on the CustOrderHist stored procedure in the model browser and then by observing its return type. The return value of CustOrderHist stored procedure is collected in this complex type. The following figure shows how this complex type looks like in the model browser:

Ok. You are now ready to call the stored procedure. Write the following C# code:

NorthwindEntities db = new NorthwindEntities();
var history = db.CustOrderHist("ALFKI");
foreach(CustOrderHist_Result item in history)
{
  string product = item.ProductName;
}

As you can see the CustOrderHist() has been called as before. This time, however, its return type is an ObjectResult of CustOrderHist_Result. You can iterate through it to grab the individual instance of CustOrderHist_Result returned. The foreach loop in the above example doesn't perform any meaningful operation on the return value apart from storing the ProductName in a string variable. You can easily replace your own login inside the loop.

In this example you used the complex type automatically created by the EF designer during the import operation. You could have created a complex type manually and then specified it in the return type dialog. In any case, if you wish to receive the return value of CustOrderHist stored procedure you must have a custom complex type. This is obvious because the returned data can't be mapped directly with any of the existing entities (such as Customer and Order).

Calling a stored procedure that returns records that map with an entity

Sometimes the stored procedure you wish to call might return data that can be mapped directly to an existing entity in the model. For example, a stored procedure may return certain records from Customers table. In this case the returned data can be mapped with the Customer entity. Consider the following stored procedure that does something similar:

CREATE PROCEDURE [dbo].[CustOrders] 
  @CustomerID nchar(5)
AS
  SELECT *
  FROM Orders
  WHERE CustomerID = @CustomerID
  ORDER BY OrderID

The CustOrders stored procedure accepts a CustomerID and returns all the Orders records from the Orders table for that CustomerID.

Import the stored procedure as before and then double click on it to open the Edit Function Import dialog. By default EF designer creates CustOrders_Result complex type. Since you know that the stored procedure return value can be mapped with Order entity you can change the return type like this:

As you can see the return type of the stored procedure has been changed to Entities and Order entity is selected in the dropdown.

To call this stored procedure you can use the following C# code:

NorthwindEntities db = new NorthwindEntities();
var result = db.CustOrders("ALFKI");
foreach(Order o in result)
{
  int i = o.OrderID;
  DateTime dt = o.OrderDate.Value;
}

In this case the return type of CustOrders() is an ObjectResult of Order. You can then iterate through the result and access individual Order instance.

Calling a stored procedure that returns multiple result sets

So far so good. However, things can be more complex that this. What if your stored procedure is returning multiple result sets? Consider the following stored procedure:

CREATE PROCEDURE [dbo].[CustAndOrders]
  @CustomerID char(5)
AS
  SELECT * from Customers where CustomerID=@CustomerID;
  SELECT * from Orders where CustomerID = @CustomerID;

The CustAndOrders stored procedure returns two result sets - the first result set from Customer table and the second result set from the Orders table. In such cases EF designer won't be able to map the return value of the stored procedure as expected. If you see the return type of CustOrders in the Edit Function Import dialog you will find it to be CustAndOrders_Result and the CustAndOrders_Result complex type contains properties for Customer data only.

To handle multiple result sets you need to manually modify the .edmx file as outlined below.

Right click on the .edmx file in the Solution Explorer and using "Open With" menu option open it in the XML editor. Locate the function import element for CustAndOrders stored procedure. It will be like this:

 <FunctionImport Name="CustAndOrders" 
  ReturnType="Collection(NorthwindModel.CustAndOrders_Result)">
  <Parameter Name="CustomerID" Mode="In" Type="String" />
</FunctionImport>

Change the above function import definition as shown below (be extra careful!) :

 <FunctionImport Name="CustAndOrders">
  <ReturnType EntitySet="Customers" Type="Collection(NorthwindModel.Customer)" />
  <ReturnType EntitySet="Orders" Type="Collection(NorthwindModel.Order)" />
  <Parameter Name="CustomerID" Mode="In" Type="String" />
</FunctionImport>

As you can see the <FunctionImport> element now has two <ReturnType> sub-elements. They specify the return types to be a collection of Customer and Order entities respectively.

Then locate function import mapping for CustAndOrders stored procedure. In its current form it will be like:

<FunctionImportMapping FunctionImportName="CustAndOrders" 
  FunctionName="NorthwindModel.Store.CustOrders">
  <ResultMapping>
    <ComplexTypeMapping TypeName="NorthwindModel.CustAndOrders_Result">
      ......
    </ComplexTypeMapping>
  </ResultMapping>
</FunctionImportMapping>

As you can see, currently the <ResultMapping> section uses <ComplexTypeMapping> to map the return value with CustAndOrders_Result. Modify the <ResultMapping> as shown below:

...
<ResultMapping>
  <EntityTypeMapping TypeName="NorthwindModel.Customer">
  </EntityTypeMapping>
</ResultMapping>
<ResultMapping>
  <EntityTypeMapping TypeName="NorthwindModel.Order">
  </EntityTypeMapping>
</ResultMapping>
...

As you can see, now you have two <ResultMapping> sections and each uses <EntityTypeMapping> to map the return values to Customer and Order entities respectively.

This completes the .edmx modifications. Make sure to save the .edmx file. The final step is to delete the CustAndOrders_Result auto-generated complex type from the model.

Now, write the following C# code to call this stored procedure:

 NorthwindEntities db = new NorthwindEntities();
var result1 = db.CustAndOrders("ALFKI");
string s1 = result1.FirstOrDefault().CompanyName;
var result2=result1.GetNextResult<Order>();
int s2 = result2.FirstOrDefault().OrderID;

When you cann CustAndOrders() method, initially the first result set is returned. You can access this result using a foreach loop or using any of the supported methods. To get hold of the second result set, you call GetNextResult() method on the first result set. You can then access the second result set as before.

 


Bipin Joshi is a software consultant, an author and a yoga mentor having 22+ years of experience in software development. He also conducts online courses in ASP.NET MVC / Core and Design Patterns. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Meditation and Mindfulness to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 12 February 2014


Tags : ADO.NET Data Access SQL Server C# Visual Studio