As an Object Relational Mapping tool, one of the Entity Framework’s
key features is that given an Entity Data Model (EDM), it makes data
access as simple as accessing properties of a class. Another is that it
can automatically generate database commands – for queries as well as
database changes – on your behalf by reading your Entity Data Model and
supporting metadata about the database schema. For many developers,
especially those beginning brand new applications with brand new
databases, removing the need to write so much redundant code and work
out how to express the database commands is a huge productivity
improvement.
However, there are many more developers who are writing applications against legacy databases that are replete with important data logic in views and stored procedures. You certainly want to leverage these for a variety of reasons – corporate mandate, you have the benefit of database gurus in house, or some other good cause –while continuing to gain the many other benefits of the Entity Framework (EF) in your application.
Fortunately, Microsoft is not at all asking you to abandon your stored procedures: you are simply being provided with more options. Store procedures provide consistency and security over the wild-west that is inserting SQL directly into your applications and the Entity Framework is not asking you to embed your SQL expressions into your applications. Instead, you will write LINQ to Entities against your entity classes (or Entity SQL expressions against your EDM) such that the Entity Framework, along with the relevant ADO.NET Data Provider, will generate SQL on the fly. It’s a much safer path and the way the SQL is generated there is a very limited chance of inviting security problems with the ORM. However, if you prefer to use your existing stored procedures, or simply prefer to create stored procedures for Entity Framework to use, this path is completely supported as well.
The Entity Framework allows you to use stored procedures in the Entity Data Model instead of, or in combination with, its automatic command generation. When designing your model, you can:
You can follow these steps to create an Entity Data Model against the database in Visual Studio 2010.
Figure 1
Notice that none of the stored procedures you selected are visible in the model in the designer. The visual model only represents your entities. Currently the stored procedures you selected are described in the Store Schema Description Layer which you can see in the raw XML or in the designer’s Model Browser. Even if the model was aware of the stored procedures, which it isn’t yet, you won’t see evidence on the design surface.
You can right click the background of the model surface and select Model Browser to see that the stored procedures are indeed accounted for.
Figure 2
As you progress through this whitepaper, you will be bringing these stored procedures into the model and then use them in your application when coding against the model and its generated classes.
Before moving on, you’ll need to make a few changes to the model; one is critical, the others are cosmetic:
Figure 3
Finally you’ll need to define the constraints of this relationship:
Figure 4
When you are finished, the model should look similar to that in Figure 5.
Figure 5
Now that you have the model in place you can begin implementing the stored procedures.
For example, if your code queries for a specific Order, edits it, and then calls Entity Framework’s SaveChanges command, the query and the required Update command will be constructed and executed by the Entity Framework. Here’s an example of such code:
Entity Framework will cause the following commands to be executed in the database, first the SELECT and then the UPDATE:
The second command is actually a batch of two statements, as the
Entity Framework ensures that the calculated column, SalesOrderNumber,
is returned to the Order instance after the update has been performed.
Now you’ll modify the model so that the Entity Framework will use the UpdateOrder stored procedure defined in the database instead of generating its own command.
You’ll do this by mapping the UpdateOrder stored procedure to the Order entity in the designer. This is referred to as Stored Procedure Mapping or Function Mapping.
When you selected database objects in the EDM Wizard, there were three stored procedures for the Order: InsertOrder, UpdateOrder, and DeleteOrder.
While you can map only a subset ofthese to the Order entity
there is an important caveat when selectively mapping the functions. The Entity Framework has an all-or-nothing policy. It cannot use stored procedures for some actions and command generation for others on a single entity type. If one of the functions is mapped but not the others, SaveChanges will fail at runtime if one of the unmapped actions is needed. For example if you map the insert and delete but not the update, you will get a runtime exception if any modifications need to be saved for that entity. Therefore the three feasible choices are to map no functions, all three functions, or all of the functions that you intend to use in your application. The recommended best practice, however, is to map all three.
For the sake of learning how to map all three types of stored procedures, you’ll map the InsertOrder, UpdateOrder, and DeleteOrder in the following walkthrough.
Here is the SQL for the InsertOrder stored procedure:
When the INSERT is completed, the query executes a SELECT to return the primary key value, SalesOrderID, of the new row.
Notice that the SalesOrderNumber is not included in the query. It is a calculated column in the database and therefore can’t be inserted.
Here’s how to map the stored procedures that are listed in the Store schema (as you saw in Figure 2) to the Order entity. It will be helpful to know that in the SSDL section of the EDMX, these stored procedures are defined in <Function> elements.
Figure 6
If you were to rerun the previous code example that queried for an order and updated its ShipDate you would get the following runtime exception because the update function is not mapped.
Cannot find the UpdateFunctionMapping for EntityType 'Order' in the mapping file.
However an insert, as shown in the following code, will work without any problems.
Here is the SQL executed in the database:
Because of the final select in the InsertOrder procedures, the Order
object’s OrderID and SalesOrderNumber properties get updated using the
values returned from the new row created in the database.
There are some additional mapping rules to be aware of before moving on:
You can map the UpdateOrder function to in the Function Mapping
window, just as you did for the insert. Again, as most of the entity
property names handily match the parameter names, the mappings will be
automatically selected by the wizard with the exception of the
SalesOrderID parameter. You’ll need to map that to the OrderID property.
There are no return values for the UpdateOrder function so this task is
complete.
If you were to run the QueryAndUpdateAnOrder method again, you will find that the stored procedure is executed rather than SQL dynamically generated by EF.
There are two checkbox columns—Use Original Value and Rows Affected Parameter — in the function mapping that you can use with Update functions. You can also use the Rows Affected Parameter checkbox with Delete functions. These settings force EF to perform concurrency checking when updating or deleting with stored procedures. You can see how this works when mapping the UpdateCustomer stored procedure to the Customer entity.
Notice that the Customer entity has a TimeStamp property which maps to a column in the database table that is a rowversion (also known as timestamp) data type. Any time a row in the table is modified, the database automatically updates this field with a new unique binary number.
You don’t need to use a rowversion value to do concurrency checking in Entity Framework. Value-based concurrency control, detects conflicts whenever some or all of the column values have changed. You can select any of the entity properties to be used for this.
Here you can see how the function mapping allows you to take advantage of that when mapping an update stored procedure.
The stored procedure uses the timestamp value as part of the filter
to identify the row. If the row was modified in the database (by another
user or process) since it was retrieved, then that timestamp value will
have changed and the row will not be found for the update.
Now let’s see how that affects the mapping. In Figure 7, the UpdateCustomer function is mapped to the Customer entity. The Use Original Value checkbox for the TimeStamp parameter/property mapping is checked. That tells the Entity Framework that when constructing the parameter values for the procedure to use the original TimeStamp value rather than the current value. The original value should be the value which originally came from the database. By sending the original TimeStamp, which is then used as part of the WHERE predicate, the command will be able to detect if the row was modified since it was retrieved by EF’s query. In that case, the UPDATE command will cause an OptimisticConcurrencyException to be thrown when SaveChanges is called. You can learn more about EF’s OptimisticConcurrencyException in MSDN topic “Saving Changes and Managing Concurrency” at http://msdn.microsoft.com/en-us/library/bb738618.aspx.
Figure 7
Alternatively, if the UpdateCustomer stored procedure used an OUTPUT parameter to return the @@RowsAffected value, for example a parameter named RowsUpdated, that parameter would appear on the Parameters list and you could check its Rows Affected Parameter checkbox. If the return value of that function is 0, an OptimisticConcurrencyException will be thrown.
Mapping a delete function is fairly straightforward. You must map the values of the parameters to existing entity properties, most likely any of the key properties. You also have the option of instructing EF to use an original value or to evaluate an OUTPUT parameter that is an integer.
The DeleteOrder stored procedure takes only the SalesOrderID as a parameter.
Figure 8
The function mapping with the delete function does not allow you to mark “Use Original Values” because EF uses the original values for deletions. The DeleteCustomer stored procedure uses the timestamp in the same way that the UpdateCustomer procedure does, therefore, you will find a TimeStamp parameter in the Parameters list for this function which is mapped in Figure 9.
Figure 9
If you wish to remove a function mapping, you can do so in the Mapping Details window by selecting <Delete> from the specific function’s dropdown list just as you did to choose one of the stored procedures.
The Entity Framework will only see that you have an entity and is not concerned if it maps to a table or to a view. You can still compose queries over the view and EF will perform relationship management and change tracking as with any other entity. The only difference is that it EF cannot generate commands to update views on its own. It needs stored procedures to be defined and mapped.
Recall that the OrderDetail entity maps to the vSalesOrderDetail database view. You can create stored procedure mappings from the DeleteOrderDetails, InsertOrderDetails and UpdateOrderDetails to the OrderDetail entity if you’d like following the same steps that you’ve learned in this section.
The Function Import wizard lets you create functions that become part of the conceptual model. While you can see these in the Model Browser, you will not see them on the design surface, which, as mentioned earlier, only displays entities and associations. The default code generator will create methods in the ObjectContext that let you call these functions.
There are some more stored procedures to bring into the model’s metadata which you can do using the Update wizard:
Start with the GetOrderDetailsForOrder. This procedure selects entire rows from the SalesOrderDetails table which match a given SalesOrderID. The OrderDetail entity matches the structure of the SalesOrderDetail table exactly. Because of this one-to-one mapping, you can create a function import from this stored procedure that returns an OrderDetail type.
Figure 10
Although this maps to the GetOrderDetailsForOrder procedure, you can give any name that you want to the function, such as GetDetailsForOrder, which is a bit simpler.
Because you know that the results of this procedure match the OrderDetail entity exactly, you can specify that in this wizard:
In the Model Browser, you’ll see that the new Function Import is part of the conceptual model, AWModel, as shown in Figure 11.
Figure 11
Additionally, if you are using the default code generation template, this function will be realized as a method in the generated ObjectContext class (AWEntities in this case). The method has two overloads and is placed in a region identified as Function Imports.
The first method takes the expected orderid parameter and returns an
ObjectResult of OrderDetail types. The second contains an additional
parameter allowing you to specify which merging strategy to use. As with
queries, the default is AppendOnly, which will add to the context any
entities returned from the database that are not already being tracked
and not bother materializing any duplicates. (See MSDN topic “Identity
Resolution, State Management, and Change Tracking” at
http://msdn.microsoft.com/en-us/library/bb896269.aspx for more information on MergeOptions).
The ObjectResult is an enumerable collection class, but it is a forward-only collection so once it has been enumerated, you cannot enumerate it again. For example, if you call ToList on the result, e.g., GetDetailsForOrder(3).ToList(), then you cannot subsequently provoke another enumeration by calling ToList again, binding the results to a control or executing a foreach over the results.
You can call the method through the context as follows:
However, you’ll get an exception stating that the DataReader (the
streamed results from the stored procedure) does not match the
OrderDetail entity. The message will provide details explaining that
there is a problem with the OrderID property.
Remember that when you created the model, you simplified some of the names of the properties. However the function import depends on the schema of the results matching the entity exactly. The property names, the types and the order of the types in the results must line up perfect with the entity or the materialization of the entity objects will fail. The stored procedure does not know about the property name modifications. Nor can you specify a mapping. This mapping is performed internally. So you’ll either need to set the property names back to match the table column names or modify the stored procedure. Most likely, a developer will prefer not to change their domain model to comply with the database, but modifying the stored procedure is not an option. Therefore the best solution may be to create a new stored procedure.
That procedure already exists in the database as GetDetailsModifiedNames. You can delete the FunctionImport and the GetOrderDetailsForOrder stored procedure directly in the Model Browser and then re-run the Update Model from Database Wizard, adding in the GetDetailsModifiedNames procedure.
Now you can create the Function Import for the new procedure. Be sure to name the function, GetDetailsForOrder as you did previously.
You should now be able to successfully run the example code.
By default, these entities returned by the Function Import will be change tracked and their changes can be persisted to the database with SaveChanges.
Note that the ADO.NET Self-Tracking Entity Generator template and Microsoft’s ADO.NET POCO Entity Generator template will also create this method in the generated class.
In this case, the function will only return a single item in the ObjectResult, the sum of all sales for the given year.
It returns FirstName, LastName, CompanyName, OrderCount, and
OrderTotal. The Entity Framework tools allow you to create a new type at
design time so that you can create functions from this type of
procedure.
The Entity Data Model supports a type called a ComplexType. A ComplexType is like an entity except for an important distinction: it does not have an EntityKey and therefore cannot be change-tracked or updated. There are a variety of ways to use ComplexTypes in your model and in your application. Here you will focus on using ComplexTypes to return types that are neither entities nor scalars.
Figure 12
In addition to the new function, you can see the new Complex Type in the Model Browser as shown in Figure 13.
Figure 13
One of the great advantages of returning a Complex Type rather than an Anonymous Type from a LINQ projection, for example, is that you now have a strongly-typed class to work with.
Here is the method that the code generator created in the AWModel class:
It returns an ObjectResult of your new type, CustomerOrderSummary.
Here’s an example of calling that method and extracting some of the
properties from the resulting rows:
An example of such a procedure is one that deletes rows from one or more tables. With EF, the ObjectContext needs to be aware of an entity before it can delete it. That means either retrieving it first from the database or creating a fake entity to add to the context so that you can delete it. There are often scenarios where you simply want to provide an available identity value and have the row deleted. There are already a number of DELETE stored procedures in the database which you can use in Function Mapping (mapping to the entities as you did earlier) or Import Functions. The DeleteOrderDetails is one such procedure that you have already brought into the metadata. You may have even mapped it to the OrderDetail entity already. Now you can also use it as a function.
The SalesOrderDetail table does not have a column such as TimeStamp used as concurrency. It takes two parameters and returns no results.
Using the steps you’ve learned for Function Import, create a Function
Import from the DeleteOrderDetails function that is listed in the Model
Browser. This time, however, you will select None as the Returns
Collection of Type option. The resulting AWEntities method is a bit
longer since it performs validation on the input parameter values:
Notice that even though you selected None as the result, the
generated method returns an integer which, according to the
ExecuteFunction documentation reports the rows affected. The stored
procedure returns the affected records by default because I have not
included SET NOCOUNT ON. If NOCOUNT was on, then result of the function
would always be -1. However, because this procedure does return the
count, the result reports the number of affected rows. That’s useful
information that you can use to determine success or failure of this
particular stored procedure.
If you are using the Self-Tracking Entities or POCO templates to generate your code, be aware that these two templates will not generate the context method for Function Imports that do not return results.
When mapping entities from views, you get strongly-typed classes whose changes can be tracked by the context and persisted back to the database. Because the store schema indicates that the data comes from a view not a table, EF will not automatically build modification commands and you need to use stored procedures to perform inserts, updates, and deletes.
When you create a function import for a stored procedure that returns entities, the context will also track changes to these entities. If the entity is mapped to a table, EF can use either mechanism (dynamically built SQL or your mapped stored procedures) for updates. If the entity is mapped to a view, again you must use stored procedures to persist back to the database.
It is also possible that your stored procedure does not return an entity. Only entities can be tracked by the context. Changes to objects that are complex types cannot be persisted to the database when your code calls SaveChanges. If you want the changes to be persisted, then you would be better off creating a view and an entity that maps to the view.
What about comparing view entities to stored procedures that return entities? They both return entities which can be change tracked and persisted to the database, but there are still two important distinctions.
The first is that a function cannot be used in a LINQ to Entities query. As you have seen, these functions return IEnumerables, not IQueryables. What does this mean? Let’s revisit the earlier code that called the GetDetailsForOrder function:
The call to context.GetDetailsForOrder(71796) is a function that is executed immediately and returns a set or results.
You could write a query against the function, for example:
However what will happen is that FirstOrDefault will be called on the
results of the GetDetailsForOrder method. This means that the entire
set of results will be returned to memory and then the first one of
those will be returned to the detail variable. You may have expected
that the FirstOrDefault part of the query would be executed in the
database and only the single item would be returned from memory. But
that is not possible because stored procedures are not composable. You
cannot, in other words, use a stored procedure within a database query.
This still may be the right option for your business logic, but beware
that if you compose a LINQ to Objects query over the function, there
will be no warning from the .NET framework compiler to alert you to
which part of the query will be executed on the server and which will be
executed on the in-memory objects. It’s important, therefore, to have
an understanding of how this works. Query profiling can also aid you in
detecting these types of problems.
Views, on the other hand, are composable so you can add any of the LINQ to Entities methods to it which will be taken into account when the store query is created – filters, sorting, projection, set methods, etc. And because they are entities, you can build up a query against them and then execute the query when the time is right.
Depending on your scenario, you may prefer a queryable, composable entity mapped to a view or you may choose an explicit, executable function that calls a stored procedure. The choice is yours.
If that stored procedure does not return results that map to an entity, but you prefer to use a view, you can create a view in the database that returns results in the same shape as the stored procedure results.
The GetCustomerNamesWithSalesOrderTotals stored procedure is an easy candidate for a view because it does not take or use any parameters:
Creating a view from this query gives you the ability to filter
(perform other query methods) the results that you don’t otherwise have
with the procedure.
Alternatively, the TotalSalesForYear, does take a parameter — a string representing a year, e.g. 2009.
Without the parameter, you would have to drop the filter and the view would lose much of it’s meaning.
It would simply be a TotalSales view, returning a sum of sales for
every single SalesOrderHeader in the database. And since it returns only
a single column (and only a single item in that column) there will be
no entity properties with which to build queries.
This particular need is best fulfilled with a stored procedure.
You can learn more about the various attributes of the function in the MSDN Documentation topic: Function Element (SSDL) (
http://msdn.microsoft.com/en-us/library/bb738614.aspx.
But there is more to the function element than what is being used in this case. There is another element within Function called CommandText where you can supply the exact SQL that would otherwise be part of a stored procedure. With that, you can add your own Function elements to the SSDL and define the procedure using the CommandText element. For example, here’s
a simple procedure that returns results based on an incoming parameter.
Because this is in the SSDL, as the Entity Framework considers it part of the database. It won’t know the difference.
There are two important caveats to be aware of. The first is that manual modifications to the SSDL get overwritten any time you use the Update Model from Database feature of the designer. That designer function recreates the SSDL from the database schema. Visual Studio 2010has no workaround for this. You may want to keep track of any manual SSDL modifications so that if they do get overwritten, you can replace them. I do this simply by copying my modifications into a text file that is part of the project along with some notes to remember the placement of the modifications. The second caveat is that the “Get Column Information” feature of the Function Import wizard is unable to detect schema from stored procedures defined in the SSDL. Instead, you will need to determine the schema yourself and manually create the complex type in the Model Browser. Then in the Function Import wizard, you will be able to choose the complex type from the drop down list of available complex types. You can learn more about creating Complex Types in the Model Browser in the MSDN topic, How to: Create and Modify Complex Types (Entity Data Model Tools), at http://msdn.microsoft.com/en-us/library/dd456820.aspx.
For this example, a new complex type called ShortProduct was created as shown in Figure 14.
Figure 14
You can specify the attributes of the properties in their Properties window, for example, that ListPrice is a Double and Name is a String.
As a result of creating the Function using the Function Import wizard, the code generator will create a new method in the AWEntities class:
You can then call this method in your code to get a list of ShortProduct objects:
Here’s an example of a stored procedure with an output parameter:
After bringing this into the model using the designer wizard, the
resulting Function marks the Mode of the output parameter as InOut:
After using the Function Import Wizard for this procedure along with
the complex type, FirstNameAndCompany, the code generator created the
following method in the AWEntities class:
Notice the second parameter is an ObjectParameter. Here’s how you can call the method from your code:
The value of the parameter is in ObjectParameter.Value. But
parameters are returned from the database at the end of the stream,
after any other results, so you can’t access any object parameters until
after the result set has streamed to the end. Here is code that assures
the results are streamed to the end and then grabs the value of the
parameter and at the same time, casting it to an integer.
The Get Column Information feature is unable to obtain the schema of
stored procedures with output parameters. As you did for the custom
stored procedure in the previous section, then, you will have to
manually create the Complex Type in the Model Wizard before running the
Function Import wizard. Then you can select that Complex Type when
creating the Function Import.
With the combination of Entity Framework’s command generation capabilities and it’s broad support for using database stored procedure, you can choose the features that best suit your needs when designing Entity Data Models and planning how to use EF in your applications.
However, there are many more developers who are writing applications against legacy databases that are replete with important data logic in views and stored procedures. You certainly want to leverage these for a variety of reasons – corporate mandate, you have the benefit of database gurus in house, or some other good cause –while continuing to gain the many other benefits of the Entity Framework (EF) in your application.
Fortunately, Microsoft is not at all asking you to abandon your stored procedures: you are simply being provided with more options. Store procedures provide consistency and security over the wild-west that is inserting SQL directly into your applications and the Entity Framework is not asking you to embed your SQL expressions into your applications. Instead, you will write LINQ to Entities against your entity classes (or Entity SQL expressions against your EDM) such that the Entity Framework, along with the relevant ADO.NET Data Provider, will generate SQL on the fly. It’s a much safer path and the way the SQL is generated there is a very limited chance of inviting security problems with the ORM. However, if you prefer to use your existing stored procedures, or simply prefer to create stored procedures for Entity Framework to use, this path is completely supported as well.
The Entity Framework allows you to use stored procedures in the Entity Data Model instead of, or in combination with, its automatic command generation. When designing your model, you can:
- specify that EF should use your stored procedures for inserting, updating, or deleting entities.
- create model functions to call query stored procedures that return entities or results that do not match any of the entities in your model.
- provide functions that call actions in the database but do not return data at all, such as performing a sweeping update across rows in a table.
- define a stored procedure within custom functions directly in your model without it existing in the database.
- map modification stored procedures to entities that are based on database views, eliminating EF’s need for direct table access.
Creating a Simple Entity Data Model to Work With
The following demos will interact with a modified version of Microsoft’s sample AdventureWorksLT database, called AdventureWorksSuperLT. This database is contained in the code samples for this article (see top of article for downloads).You can follow these steps to create an Entity Data Model against the database in Visual Studio 2010.
- Create a new Class Library project called SuperLTModel.
- Add an existing item to the project and navigate to the folder where you saved the AdventureWorksSuperLT database. Be sure to change the file type selection on the lower right corner of the dialog to Data Files so that you can see the database.
- Select the AdventureWorksSuperLT database and click the Add button.
Adding the database to the project in this way will cause Visual Studio to prompt you to create a strongly typed Dataset or an EDM. - In the Data Source Configuration Wizard’s Choose a Database Model screen select Entity Data Model to start the Entity Data Model Wizard.
- The Choose Your Data Connection screen will have the newly added database pre-selected.
- Rename the Entity Connection settings at the bottom of the screen from the default to AWEntities.
- The next screen of the wizard will invite you to select Database Objects to be used in your model.
- Expand Tables and select the following tables to be included:
Customer and SalesOrderHeader - Expand Views and select the vSalesOrderDetail.
- Expand Stored Procedures and select the following eight stored procedures:
DeleteCustomer, DeleteOrder, DeleteOrderDetails, InsertOrder, InsertOrderDetails, UpdateCustomer, UpdateOrder and UpdateOrderDetails. - Rename the Model Namespace to AWModel.
- Click Finish.
Figure 1
Notice that none of the stored procedures you selected are visible in the model in the designer. The visual model only represents your entities. Currently the stored procedures you selected are described in the Store Schema Description Layer which you can see in the raw XML or in the designer’s Model Browser. Even if the model was aware of the stored procedures, which it isn’t yet, you won’t see evidence on the design surface.
You can right click the background of the model surface and select Model Browser to see that the stored procedures are indeed accounted for.
Figure 2
As you progress through this whitepaper, you will be bringing these stored procedures into the model and then use them in your application when coding against the model and its generated classes.
Before moving on, you’ll need to make a few changes to the model; one is critical, the others are cosmetic:
- First, make the following entity and property name changes:
- Change SalesOrderHeader to Order.
- Change vSalesOrderDetail to OrderDetail.
- Change the SalesOrderHeaders property in the Customer entity to Orders.
- Change the SalesOrderID property in Order to OrderID
- Change the SalesOrderID property in OrderDetail to OrderID
- Change the SalesOrderDetailID property in OrderDetail to OrderDetailID.
- In
the OrderDetail, right click on the LineTotal property and uncheck
EntityKey in the menu. You’ll notice the little key icon next to that
property disappears as a result. When mapping to database views, the
wizard infers entity keys from all of the non-nullable columns. By
unchecking the EntityKey flag, you’ve removed the LineTotal from the
EntityKey.
Because this model uses the SalesOrderDetail view rather than the table, you’ll need to manually create the relationship between that and the Order entity: - Right click the Order entity and select Add Association.
- In the Add Association wizard ensure that the details match Figure 3. Note that the Add foreign key properties checkbox is unchecked. The foreign keys already exist in the OrderDetail entity, so you don’t need to add them again.
Figure 3
Finally you’ll need to define the constraints of this relationship:
- Right click the new association line between Order and Order Detail and select Properties.
- In the Properties window, click on the Referential Constraint property which will respond by displaying an ellipses button.
- Click that button and define the constraint as shown in Figure 4.
Figure 4
When you are finished, the model should look similar to that in Figure 5.
Figure 5
Now that you have the model in place you can begin implementing the stored procedures.
Mapping Modification Stored Procedures to Entities
By default, the Entity Framework will generate the database queries and update commands when your code interacts with the model.For example, if your code queries for a specific Order, edits it, and then calls Entity Framework’s SaveChanges command, the query and the required Update command will be constructed and executed by the Entity Framework. Here’s an example of such code:
- var order = context.Orders.FirstOrDefault(o => o.OrderID == 71845);
- order.ShipDate = DateTime.Now;
- context.SaveChanges();
- SELECT TOP (1)
- [Extent1].[SalesOrderID] AS [SalesOrderID],
- [Extent1].[OrderDate] AS [OrderDate],
- [Extent1].[DueDate] AS [DueDate],
- [Extent1].[OnlineOrderFlag] AS [OnlineOrderFlag],
- [Extent1].[SalesOrderNumber] AS [SalesOrderNumber],
- [Extent1].[PurchaseOrderNumber] AS [PurchaseOrderNumber],
- [Extent1].[AccountNumber] AS [AccountNumber],
- [Extent1].[CustomerID] AS [CustomerID],
- [Extent1].[BillToAddressID] AS [BillToAddressID],
- [Extent1].[CreditCardApprovalCode] AS [CreditCardApprovalCode],
- [Extent1].[SubTotal] AS [SubTotal],
- [Extent1].[Comment] AS [Comment],
- [Extent1].[ModifiedDate] AS [ModifiedDate],
- [Extent1].[ShipDate] AS [ShipDate]
- FROM [SalesLT].[SalesOrderHeader] AS [Extent1]
- WHERE 71845 = [Extent1].[SalesOrderID]
- exec sp_executesql N'update [SalesLT].[SalesOrderHeader]
- set [ShipDate] = @0
- where ([SalesOrderID] = @1)
- select [SalesOrderNumber]
- from [SalesLT].[SalesOrderHeader]
- where @@ROWCOUNT > 0 and [SalesOrderID] = @1',
- N'@0 datetime2(7),@1 int',
- @0='2011-02-17 11:22:28.5829448',@1=71845
Now you’ll modify the model so that the Entity Framework will use the UpdateOrder stored procedure defined in the database instead of generating its own command.
You’ll do this by mapping the UpdateOrder stored procedure to the Order entity in the designer. This is referred to as Stored Procedure Mapping or Function Mapping.
When you selected database objects in the EDM Wizard, there were three stored procedures for the Order: InsertOrder, UpdateOrder, and DeleteOrder.
While you can map only a subset ofthese to the Order entity
there is an important caveat when selectively mapping the functions. The Entity Framework has an all-or-nothing policy. It cannot use stored procedures for some actions and command generation for others on a single entity type. If one of the functions is mapped but not the others, SaveChanges will fail at runtime if one of the unmapped actions is needed. For example if you map the insert and delete but not the update, you will get a runtime exception if any modifications need to be saved for that entity. Therefore the three feasible choices are to map no functions, all three functions, or all of the functions that you intend to use in your application. The recommended best practice, however, is to map all three.
For the sake of learning how to map all three types of stored procedures, you’ll map the InsertOrder, UpdateOrder, and DeleteOrder in the following walkthrough.
Here is the SQL for the InsertOrder stored procedure:
- ALTER PROC [dbo].[InsertOrder]
- @OrderDate datetime,
- @DueDate datetime,
- @OnlineOrderFlag flag,
- @PurchaseOrderNumber ordernumber,
- @AccountNumber accountnumber,
- @CustomerID int,
- @BillToAddressID int,
- @CreditCardApprovalCode varchar(15),
- @SubTotal money,
- @Comment nvarchar(MAX),
- @ModifiedDate datetime,
- @ShipDate date
- AS
- INSERT INTO [SalesLT].[SalesOrderHeader] ([OrderDate], [DueDate], [OnlineOrderFlag], [PurchaseOrderNumber], AccountNumber], [CustomerID], [BillToAddressID], [CreditCardApprovalCode], [SubTotal], [Comment], [ModifiedDate], [ShipDate])
- SELECT @OrderDate, @DueDate, @OnlineOrderFlag, @PurchaseOrderNumber, @AccountNumber, @CustomerID, @BillToAddressID, @CreditCardApprovalCode, @SubTotal, @Comment, @ModifiedDate, @ShipDate
- SELECT [SalesOrderID]
- FROM [SalesLT].[SalesOrderHeader]
- WHERE [SalesOrderID] = SCOPE_IDENTITY()
Notice that the SalesOrderNumber is not included in the query. It is a calculated column in the database and therefore can’t be inserted.
Here’s how to map the stored procedures that are listed in the Store schema (as you saw in Figure 2) to the Order entity. It will be helpful to know that in the SSDL section of the EDMX, these stored procedures are defined in <Function> elements.
- In the designer, right click on the Order entity and select Stored Procedure mapping.
- Click <Select Insert Function> and then click the drop down arrow that appears.
This exposes the list of all Functions found in the Store metadata. - Select
InsertOrder from the list. The designer will do its best job of
matching the stored procedure’s parameters with the entity properties
using the names. In this case, since all of the property names match the
parameter names, it maps every one correctly so you don’t need to make
any changes.
One thing that the designer was not able to map is the newly generated SalesOrderID returned by the stored procedure. This is because the designer is not able to automatically detect the name of the field being returned. - Under the Result Column Bindings section, click <Add Result Binding> and enter SalesOrderID. The designer should automatically select the entity key property, OrderID, for this final mapping.
Figure 6
If you were to rerun the previous code example that queried for an order and updated its ShipDate you would get the following runtime exception because the update function is not mapped.
Cannot find the UpdateFunctionMapping for EntityType 'Order' in the mapping file.
However an insert, as shown in the following code, will work without any problems.
- var order = new Order {
- OrderDate = DateTime.Today,
- PurchaseOrderNumber = "ABC",
- CustomerID = 3,
- ModifiedDate = DateTime.Now,
- OnlineOrderFlag = true };
- context.Orders.AddObject(order);
- context.SaveChanges();
- exec [dbo].[InsertOrder] @OrderDate = '2010-09-24 00:00:00', @DueDate = NULL,
- @OnlineOrderFlag = 1, @SalesOrderNumber = NULL,
- @PurchaseOrderNumber = N'ABC', @AccountNumber = NULL, @CustomerID = 3,
- @BillToAddressID = NULL, @CreditCardApprovalCode = NULL,
- @SubTotal = 0.0000, @Comment = NULL,
- @ModifiedDate = '2010-09-24 14:07:53.5913857', @ShipDate = NULL
There are some additional mapping rules to be aware of before moving on:
- You must map every parameter of the stored procedure
- Parameters can be mapped only to entity properties. You cannot map the parameters to a scalar value or a function. As an example, you cannot map the ModifiedDate parameter to DateTime.Now .
- ALTER PROC [dbo].[UpdateOrder]
- @SalesOrderID int,
- @OrderDate datetime,
- @DueDate datetime,
- @OnlineOrderFlag flag,
- @PurchaseOrderNumber ordernumber,
- @AccountNumber accountnumber,
- @CustomerID int,
- @BillToAddressID int,
- @CreditCardApprovalCode varchar(15),
- @SubTotal money,
- @Comment nvarchar(MAX),
- @ModifiedDate datetime,
- @ShipDate date
- AS
- SET NOCOUNT ON
- UPDATE [SalesLT].[SalesOrderHeader]
- SET [OrderDate] = @OrderDate, [DueDate] = @DueDate,
- [OnlineOrderFlag] = @OnlineOrderFlag,
- [PurchaseOrderNumber] = @PurchaseOrderNumber,
- [AccountNumber] = @AccountNumber, [CustomerID] = @CustomerID,
- [BillToAddressID] = @BillToAddressID,
- [CreditCardApprovalCode] = @CreditCardApprovalCode,
- [SubTotal] = @SubTotal, [Comment] = @Comment,
- [ModifiedDate] = @ModifiedDate, [ShipDate] = @ShipDate
- WHERE [SalesOrderID] = @SalesOrderID
If you were to run the QueryAndUpdateAnOrder method again, you will find that the stored procedure is executed rather than SQL dynamically generated by EF.
There are two checkbox columns—Use Original Value and Rows Affected Parameter — in the function mapping that you can use with Update functions. You can also use the Rows Affected Parameter checkbox with Delete functions. These settings force EF to perform concurrency checking when updating or deleting with stored procedures. You can see how this works when mapping the UpdateCustomer stored procedure to the Customer entity.
Notice that the Customer entity has a TimeStamp property which maps to a column in the database table that is a rowversion (also known as timestamp) data type. Any time a row in the table is modified, the database automatically updates this field with a new unique binary number.
You don’t need to use a rowversion value to do concurrency checking in Entity Framework. Value-based concurrency control, detects conflicts whenever some or all of the column values have changed. You can select any of the entity properties to be used for this.
Here you can see how the function mapping allows you to take advantage of that when mapping an update stored procedure.
- ALTER PROCEDURE UpdateCustomer
- @CustomerID int,
- @Title nvarchar(8),
- @FirstName nvarchar(50),
- @MiddleName nvarchar(50),
- @LastName nvarchar(50),
- @Suffix nvarchar(10),
- @CompanyName nvarchar(128),
- @SalesPerson nvarchar(256),
- @EmailAddress nvarchar(50),
- @Phone nvarchar(25),
- @ModifiedDate datetime,
- @TimeStamp timestamp
- AS
- SET NOCOUNT ON
- UPDATE [AdventureWorksLT].[SalesLT].[Customer]
- SET [Title] = @Title,
- [FirstName] = @FirstName,
- [MiddleName] = @MiddleName,
- [LastName] = @LastName,
- [Suffix] = @Suffix,
- [CompanyName] = @CompanyName,
- [SalesPerson] = @SalesPerson,
- [EmailAddress] = @EmailAddress,
- [Phone] = @Phone,
- [ModifiedDate] = @ModifiedDate
- WHERE CustomerID = @CustomerID
- and TimeStamp = @TimeStamp
Now let’s see how that affects the mapping. In Figure 7, the UpdateCustomer function is mapped to the Customer entity. The Use Original Value checkbox for the TimeStamp parameter/property mapping is checked. That tells the Entity Framework that when constructing the parameter values for the procedure to use the original TimeStamp value rather than the current value. The original value should be the value which originally came from the database. By sending the original TimeStamp, which is then used as part of the WHERE predicate, the command will be able to detect if the row was modified since it was retrieved by EF’s query. In that case, the UPDATE command will cause an OptimisticConcurrencyException to be thrown when SaveChanges is called. You can learn more about EF’s OptimisticConcurrencyException in MSDN topic “Saving Changes and Managing Concurrency” at http://msdn.microsoft.com/en-us/library/bb738618.aspx.
Figure 7
Alternatively, if the UpdateCustomer stored procedure used an OUTPUT parameter to return the @@RowsAffected value, for example a parameter named RowsUpdated, that parameter would appear on the Parameters list and you could check its Rows Affected Parameter checkbox. If the return value of that function is 0, an OptimisticConcurrencyException will be thrown.
Mapping a delete function is fairly straightforward. You must map the values of the parameters to existing entity properties, most likely any of the key properties. You also have the option of instructing EF to use an original value or to evaluate an OUTPUT parameter that is an integer.
The DeleteOrder stored procedure takes only the SalesOrderID as a parameter.
- ALTER PROC [dbo].[DeleteOrder]
- @SalesOrderID int
- AS
- SET NOCOUNT ON
- DELETE
- FROM [SalesLT].[SalesOrderHeader]
- WHERE [SalesOrderID] = @SalesOrderID
Figure 8
The function mapping with the delete function does not allow you to mark “Use Original Values” because EF uses the original values for deletions. The DeleteCustomer stored procedure uses the timestamp in the same way that the UpdateCustomer procedure does, therefore, you will find a TimeStamp parameter in the Parameters list for this function which is mapped in Figure 9.
Figure 9
If you wish to remove a function mapping, you can do so in the Mapping Details window by selecting <Delete> from the specific function’s dropdown list just as you did to choose one of the stored procedures.
Mapping Functions to Entities that Map to Database Views
So far you have mapped modification functions to entities that map directly to database tables. You can do the same for entities that map to database views. This is an interesting feature because it enables you to build models completely from views and stored procedures without exposing the tables for dynamic queries. In an enterprise where database access is limited to views and stored procedures, this means that you can, indeed, use the Entity Framework. Additionally, developers working with poorly designed databases can avoid struggling to map entities to the database tables by creating more convenient views and stored procedures that they can use in their model.The Entity Framework will only see that you have an entity and is not concerned if it maps to a table or to a view. You can still compose queries over the view and EF will perform relationship management and change tracking as with any other entity. The only difference is that it EF cannot generate commands to update views on its own. It needs stored procedures to be defined and mapped.
Recall that the OrderDetail entity maps to the vSalesOrderDetail database view. You can create stored procedure mappings from the DeleteOrderDetails, InsertOrderDetails and UpdateOrderDetails to the OrderDetail entity if you’d like following the same steps that you’ve learned in this section.
Using Import Functions to Map Stored Procedures
So far you’ve seen how to work with modification stored procedures that map directly to entities in your model. Entity Framework has a feature called Function Import that lets you work with other types of stored procs. You can import procedures that return data, whether that is a set of entities, a set of scalar values, or even a set of results for which you don’t have a matching type in your model. Function Import also lets you map procedures that affect the database even if they don’t return any data at all.The Function Import wizard lets you create functions that become part of the conceptual model. While you can see these in the Model Browser, you will not see them on the design surface, which, as mentioned earlier, only displays entities and associations. The default code generator will create methods in the ObjectContext that let you call these functions.
There are some more stored procedures to bring into the model’s metadata which you can do using the Update wizard:
- Right click on the model’s design surface background.
- Select Update Model From Database.
- In the Add page of the wizard, expand Stored Procedures.
- Select the following four procedures: GetCustomerNamesWithSalesOrderDetails, GetOrderDetailsForOrder, GetDetailsModifiedNames and TotalSalesForYear.
Importing Stored Procedures that Return Entities
It is possible to return entities from stored procedures and, by default, these will get change-tracked just as entities returned by queries.Start with the GetOrderDetailsForOrder. This procedure selects entire rows from the SalesOrderDetails table which match a given SalesOrderID. The OrderDetail entity matches the structure of the SalesOrderDetail table exactly. Because of this one-to-one mapping, you can create a function import from this stored procedure that returns an OrderDetail type.
- Begin by right-clicking the GetOrderDeailsForOrder Stored Procedure in the AWModel.Store setion of the Model Browser.
- Select Add Function Import… from the menu.
- The Add Function Import wizard will appear as shown in Figure 10.
Figure 10
Although this maps to the GetOrderDetailsForOrder procedure, you can give any name that you want to the function, such as GetDetailsForOrder, which is a bit simpler.
Because you know that the results of this procedure match the OrderDetail entity exactly, you can specify that in this wizard:
- Select Entities under Returns a Collection Of.
- In the Entities’ drop down, select OrderDetail.
- Click OK.
In the Model Browser, you’ll see that the new Function Import is part of the conceptual model, AWModel, as shown in Figure 11.
Figure 11
Additionally, if you are using the default code generation template, this function will be realized as a method in the generated ObjectContext class (AWEntities in this case). The method has two overloads and is placed in a region identified as Function Imports.
- public ObjectResult<OrderDetail> GetDetailsForOrder
- (Nullable<global::System.Int32> orderid)
- {
- ObjectParameter orderidParameter;
- if (orderid.HasValue)
- {
- orderidParameter = new ObjectParameter("orderid", orderid);
- }
- else
- {
- orderidParameter = new ObjectParameter("orderid", typeof(global::System.Int32));
- }
- return base.ExecuteFunction<OrderDetail>("GetDetailsForOrder", orderidParameter);
- }
- public ObjectResult<OrderDetail> GetDetailsForOrder(Nullable<global::System.Int32>
- orderid, MergeOption mergeOption)
- {
- ObjectParameter orderidParameter;
- if (orderid.HasValue)
- {
- orderidParameter = new ObjectParameter("orderid", orderid);
- }
- else
- {
- orderidParameter = new ObjectParameter("orderid", typeof(global::System.Int32));
- }
- return base.ExecuteFunction<OrderDetail>("GetDetailsForOrder", mergeOption,
- orderidParameter);
- }
The ObjectResult is an enumerable collection class, but it is a forward-only collection so once it has been enumerated, you cannot enumerate it again. For example, if you call ToList on the result, e.g., GetDetailsForOrder(3).ToList(), then you cannot subsequently provoke another enumeration by calling ToList again, binding the results to a control or executing a foreach over the results.
You can call the method through the context as follows:
- var context = new AWEntities();
- ObjectResult<OrderDetail> orderDetailEnumerable = context.GetDetailsForOrder(71796);
- List<OrderDetail> details = orderDetailEnumerable.ToList();
Remember that when you created the model, you simplified some of the names of the properties. However the function import depends on the schema of the results matching the entity exactly. The property names, the types and the order of the types in the results must line up perfect with the entity or the materialization of the entity objects will fail. The stored procedure does not know about the property name modifications. Nor can you specify a mapping. This mapping is performed internally. So you’ll either need to set the property names back to match the table column names or modify the stored procedure. Most likely, a developer will prefer not to change their domain model to comply with the database, but modifying the stored procedure is not an option. Therefore the best solution may be to create a new stored procedure.
That procedure already exists in the database as GetDetailsModifiedNames. You can delete the FunctionImport and the GetOrderDetailsForOrder stored procedure directly in the Model Browser and then re-run the Update Model from Database Wizard, adding in the GetDetailsModifiedNames procedure.
Now you can create the Function Import for the new procedure. Be sure to name the function, GetDetailsForOrder as you did previously.
You should now be able to successfully run the example code.
By default, these entities returned by the Function Import will be change tracked and their changes can be persisted to the database with SaveChanges.
Note that the ADO.NET Self-Tracking Entity Generator template and Microsoft’s ADO.NET POCO Entity Generator template will also create this method in the generated class.
Importing Stored Procedures that Return Scalar Values
Now let’s take a look at another type of stored procedure – those that return scalar values. The TotalSalesForYear method returns a single currency value. If you return to Figure 10, you’ll see that “Scalars” is a return type option in the Function Import wizard.- Following the steps for importing the GetOrderDetailForOrder procedure, import the TotalSalesForYear procedure.
- Change the Function Import Name to GetTotalSalesForYear
- Select Returns a Collection Of Scalars
- In the Scalars drop down, select Decimal.
- Click OK to complete the import.
- public ObjectResult<Nullable<global::System.Decimal>>
- GetTotalSalesForYear(global::System.String fiscalyear)
- {
- ObjectParameter fiscalyearParameter;
- if (fiscalyear != null)
- {
- fiscalyearParameter = new ObjectParameter("fiscalyear", fiscalyear);
- }
- else
- {
- fiscalyearParameter = new ObjectParameter("fiscalyear",
- typeof(global::System.String));
- }
- return base.ExecuteFunction<Nullable<global::System.Decimal>>
- ("GetTotalSalesForYear", fiscalyearParameter);
- }
- var context = new AWEntities();
- decimal? TotalSales = context.GetTotalSalesForYear("2004").FirstOrDefault();
- Result: 897259.5138
Importing Stored Procedures that Return Types Other than Entities
Often, your stored procedure will return data in a schema that is completely new and won’t match any of your entities. The GetCustomerNamesWithSalesOrderTotals procedure is an example of this.
- ALTER Procedure GetCustomerNamesWithSalesOrderTotals
- AS
- SELECT Min(FirstName) as FirstName,
- MIN(LastName) as LastName,
- Min(CompanyName) as CompanyName,
- Count(SalesOrderHeader.SalesOrderID) as OrderCount,
- SUM(SalesLT.SalesOrderHeader.SubTotal) as OrderTotal
- FROM SalesLT.Customer
- INNER JOIN SalesLT.SalesOrderHeader
- ON SalesLT.Customer.CustomerID = SalesLT.SalesOrderHeader.CustomerID
- GROUP by SalesLT.Customer.CustomerID
The Entity Data Model supports a type called a ComplexType. A ComplexType is like an entity except for an important distinction: it does not have an EntityKey and therefore cannot be change-tracked or updated. There are a variety of ways to use ComplexTypes in your model and in your application. Here you will focus on using ComplexTypes to return types that are neither entities nor scalars.
- From the Model Browser, start the Function Import Wizard for the GetCustomerNamesWithSalesOrderTotals function.
- Change the Function Import Name to GetCustomerOrderSummary.
- Click the Get Column Information button. This will cause the designer to execute the stored procedure so that it can read the schema of the result set. The schema will be displayed in the grid below the button.
- Click the Create New Complex Type button below the grid.
As a result, the Complex Collection option will be selected and a default name which is the Function Import Name plus “_Result” will be entered as the selected type. - Rename the new complex type in the text box to CustomerOrderSummary.
- Click the OK button.
Figure 12
In addition to the new function, you can see the new Complex Type in the Model Browser as shown in Figure 13.
Figure 13
One of the great advantages of returning a Complex Type rather than an Anonymous Type from a LINQ projection, for example, is that you now have a strongly-typed class to work with.
Here is the method that the code generator created in the AWModel class:
- public ObjectResult<CustomerOrderSummary> GetCustomerOrderSummary()
- {
- return base.ExecuteFunction<CustomerOrderSummary>("GetCustomerOrderSummary");
- }
- var context = new AWEntities();
- foreach (CustomerOrderSummary cos in context.GetCustomerOrderSummary())
- {
- Console.WriteLine("{0} # Orders: {1} Total Sales: {2:C}",
- cos.CompanyName, cos.OrderCount, cos.OrderTotal);
- }
- Subset of Results:
- Trailblazing Sports # Orders: 3 Total Sales: $46,237.01
- Paints and Solvents Company # Orders: 1 Total Sales: $12,685.89
- Channel Outlet # Orders: 1 Total Sales: $550.39
- Thrifty Parts and Sales # Orders: 2 Total Sales: $1,836.49
Function Imports for Modification Stored Procedures
The stored procedures imported thus far are ones which only return data, but they don’t necessarily need to be. The Entity Framework doesn’t care what the procedure is doing in the database: it is only concerned with input parameters and results, if any. Therefore you can also import stored procedures that make modifications to the database. These procedures may also return results — whether those are entities, scalars, complex types, or as you can see with the last of the return options in the Add Function Import wizard, procedures that return nothing at all.An example of such a procedure is one that deletes rows from one or more tables. With EF, the ObjectContext needs to be aware of an entity before it can delete it. That means either retrieving it first from the database or creating a fake entity to add to the context so that you can delete it. There are often scenarios where you simply want to provide an available identity value and have the row deleted. There are already a number of DELETE stored procedures in the database which you can use in Function Mapping (mapping to the entities as you did earlier) or Import Functions. The DeleteOrderDetails is one such procedure that you have already brought into the metadata. You may have even mapped it to the OrderDetail entity already. Now you can also use it as a function.
The SalesOrderDetail table does not have a column such as TimeStamp used as concurrency. It takes two parameters and returns no results.
- ALTER PROC [dbo].[DeleteOrderDetails]
- @SalesOrderID int,
- @SalesOrderDetailID int
- AS
- DELETE
- FROM [SalesLT].[SalesOrderDetail]
- WHERE [SalesOrderID] = @SalesOrderID
- AND [SalesOrderDetailID] = @SalesOrderDetailID
- public int DeleteOrderDetails(Nullable<global::System.Int32> salesOrderID,
- Nullable<global::System.Int32> salesOrderDetailID)
- {
- ObjectParameter salesOrderIDParameter;
- if (salesOrderID.HasValue)
- {
- salesOrderIDParameter = new ObjectParameter("SalesOrderID", salesOrderID);
- }
- else
- {
- salesOrderIDParameter = new ObjectParameter("SalesOrderID", typeof(global::System.Int32));
- }
- ObjectParameter salesOrderDetailIDParameter;
- if (salesOrderDetailID.HasValue)
- {
- salesOrderDetailIDParameter = new ObjectParameter("SalesOrderDetailID", salesOrderDetailID);
- }
- else
- {
- salesOrderDetailIDParameter = new ObjectParameter("SalesOrderDetailID", typeof(global::System.Int32));
- }
- return base.ExecuteFunction("DeleteOrderDetails", salesOrderIDParameter, salesOrderDetailIDParameter);
- }
If you are using the Self-Tracking Entities or POCO templates to generate your code, be aware that these two templates will not generate the context method for Function Imports that do not return results.
Comparing the Use of Query Stored Procedures to Views in an Entity Data Model
You’ve seen now that you can return entities or new types from stored procedures as well as map entities to views. There are some noteworthy differences between using views or stored procedures in an Entity Data Model.When mapping entities from views, you get strongly-typed classes whose changes can be tracked by the context and persisted back to the database. Because the store schema indicates that the data comes from a view not a table, EF will not automatically build modification commands and you need to use stored procedures to perform inserts, updates, and deletes.
When you create a function import for a stored procedure that returns entities, the context will also track changes to these entities. If the entity is mapped to a table, EF can use either mechanism (dynamically built SQL or your mapped stored procedures) for updates. If the entity is mapped to a view, again you must use stored procedures to persist back to the database.
It is also possible that your stored procedure does not return an entity. Only entities can be tracked by the context. Changes to objects that are complex types cannot be persisted to the database when your code calls SaveChanges. If you want the changes to be persisted, then you would be better off creating a view and an entity that maps to the view.
What about comparing view entities to stored procedures that return entities? They both return entities which can be change tracked and persisted to the database, but there are still two important distinctions.
The first is that a function cannot be used in a LINQ to Entities query. As you have seen, these functions return IEnumerables, not IQueryables. What does this mean? Let’s revisit the earlier code that called the GetDetailsForOrder function:
- var context = new AWEntities();
- ObjectResult<OrderDetail> orderDetailEnumerable = context.GetDetailsForOrder(71796);
- List<OrderDetail> details = orderDetailEnumerable.ToList();
You could write a query against the function, for example:
- OrderDetail detail = context.GetDetailsForOrder(71796).FirstOrDefault();
Views, on the other hand, are composable so you can add any of the LINQ to Entities methods to it which will be taken into account when the store query is created – filters, sorting, projection, set methods, etc. And because they are entities, you can build up a query against them and then execute the query when the time is right.
Depending on your scenario, you may prefer a queryable, composable entity mapped to a view or you may choose an explicit, executable function that calls a stored procedure. The choice is yours.
If that stored procedure does not return results that map to an entity, but you prefer to use a view, you can create a view in the database that returns results in the same shape as the stored procedure results.
The GetCustomerNamesWithSalesOrderTotals stored procedure is an easy candidate for a view because it does not take or use any parameters:
- SELECT Min(FirstName) as FirstName,
- MIN(LastName) as LastName,
- Min(CompanyName) as CompanyName,
- Count(SalesOrderHeader.SalesOrderID) as OrderCount,
- SUM(SalesLT.SalesOrderHeader.SubTotal) as OrderTotal
- FROM SalesLT.Customer
- INNER JOIN SalesLT.SalesOrderHeader ON SalesLT.Customer.CustomerID = SalesLT.SalesOrderHeader.CustomerID
- GROUP by SalesLT.Customer.CustomerID
Alternatively, the TotalSalesForYear, does take a parameter — a string representing a year, e.g. 2009.
- SELECT SUM(SubTotal) AS TotalSales
- FROM SalesLT.SalesOrderHeader
- WHERE YEAR(OrderDate) = @fiscalyear
- SELECT SUM(SubTotal) AS TotalSales
- FROM SalesLT.SalesOrderHeader
This particular need is best fulfilled with a stored procedure.
Defining Functions in the Model’s Metadata
There are times when a particular database stored procedure would be a great convenience but it does not exist in your database nor is it possible to add it. Consider that the store schema (SSDL) portion of the model’s metadata contains descriptions of what is in the database. In the case of a database stored procedure, for example, the GetDetailsModifiedNames, which you worked with in the previous section, it is specified in the SSDL as follows:
- <Function Name="GetDetailsModifiedNames" Aggregate="false" BuiltIn="false"
- NiladicFunction="false" IsComposable="false"
- ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
- <Parameter Name="orderid" Type="int" Mode="In" />
- </Function>
But there is more to the function element than what is being used in this case. There is another element within Function called CommandText where you can supply the exact SQL that would otherwise be part of a stored procedure. With that, you can add your own Function elements to the SSDL and define the procedure using the CommandText element. For example, here’s
a simple procedure that returns results based on an incoming parameter.
- <Function Name="ProductsFromName" IsComposable="false">
- <CommandText>
- SELECT ProductNumber, Name, ListPrice, ProductID
- FROM SalesLT.Product
- where Name like '%' + @NameContains + '%';
- </CommandText>
- <Parameter Name="NameContains" Type="nvarchar" Mode="In"/>
- </Function>
There are two important caveats to be aware of. The first is that manual modifications to the SSDL get overwritten any time you use the Update Model from Database feature of the designer. That designer function recreates the SSDL from the database schema. Visual Studio 2010has no workaround for this. You may want to keep track of any manual SSDL modifications so that if they do get overwritten, you can replace them. I do this simply by copying my modifications into a text file that is part of the project along with some notes to remember the placement of the modifications. The second caveat is that the “Get Column Information” feature of the Function Import wizard is unable to detect schema from stored procedures defined in the SSDL. Instead, you will need to determine the schema yourself and manually create the complex type in the Model Browser. Then in the Function Import wizard, you will be able to choose the complex type from the drop down list of available complex types. You can learn more about creating Complex Types in the Model Browser in the MSDN topic, How to: Create and Modify Complex Types (Entity Data Model Tools), at http://msdn.microsoft.com/en-us/library/dd456820.aspx.
For this example, a new complex type called ShortProduct was created as shown in Figure 14.
Figure 14
You can specify the attributes of the properties in their Properties window, for example, that ListPrice is a Double and Name is a String.
As a result of creating the Function using the Function Import wizard, the code generator will create a new method in the AWEntities class:
- public ObjectResult<ShortProduct> GetProductsFromName(global::System.String nameContains)
- {
- ObjectParameter nameContainsParameter;
- if (nameContains != null)
- {
- nameContainsParameter = new ObjectParameter("NameContains", nameContains);
- }
- else
- {
- nameContainsParameter = new ObjectParameter("NameContains",
- typeof(global::System.String));
- }
- return base.ExecuteFunction<ShortProduct>("GetProductsFromName",
- nameContainsParameter);
- }
- var context = new AWEntities();
- List<ShortProduct> products = context.GetProductsFromName("Wheel").ToList();
Function Imports and Output Parameters
You can create Functions from stored procedures that contain output parameters whether your procedure returns entities, scalars or complex types or those designed only to return output parameter(s) but no result sets. Following is an example of a stored procedure that returns projected results and a single output parameter.Here’s an example of a stored procedure with an output parameter:
- ALTER PROCEDURE [dbo].[GetCountByLastName] (
- @LastName NVARCHAR(50),
- @LastNameCount INT output )
- AS
- SELECT FirstName, CompanyName
- FROM Customers
- Where LastName=@LastName
- SET @LastNameCount= @@ROWCOUNT
- <Function Name="GetCountByLastName" Aggregate="false" BuiltIn="false"
- NiladicFunction="false" IsComposable="false"
- ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
- <Parameter Name="LastName" Type="nvarchar" Mode="In" />
- <Parameter Name="LastNameCount" Type="int" Mode="InOut" />
- </Function>
- public ObjectResult<FirstNameAndCompany> GetCountByLastName
- (global::System.String lastName, ObjectParameter lastNameCount)
- {
- ObjectParameter lastNameParameter;
- if (lastName != null)
- {
- lastNameParameter = new ObjectParameter("LastName", lastName);
- }
- else
- {
- lastNameParameter = new ObjectParameter("LastName", typeof(global::System.String));
- }
- return base.ExecuteFunction<FirstNameAndCompany>
- ("GetCountByLastName", lastNameParameter, lastNameCount);
- }
- var outputParam = new ObjectParameter("lastNameCount", new Int32());
- var results = context.GetCountByLastName("Adams", outputParam);
- var resultsList = results.ToList();//push through results
- var outputValue=(int)outputParam.Value);
Working with Stored Procedures that Return Multiple Resultsets
The Entity Framework does not directly support stored procedures that return multiple resultsets, though it is possible to get this behavior in EF4 by using the Translate method and ExecuteStoreReader. However, the Entity Framework team has provided sample utilities, the EFExtensions, on the MSDN Code Gallery that will enable you to consume multiple resultsets. There are other features provided by the EFExtensions, such as renaming columns (to solve a problem which was discussed earlier in this whitepaper). To learn more and download the extensions, visit http://code.msdn.microsoft.com/EFExtensions. Note that the extensions and other samples provided on the Code Gallery, while very useful, are not supported by Microsoft.Summary
Although command generation is one of Entity Framework’s prominent features, this doesn’t prevent you from leveraging database stored procedures. You’ve now seen that there are a number of ways that you can benefit from an Entity Data Model and EF’s capabilities while using your own stored procedures. You can map modification stored procedures to entities so that EF will automatically use those any inserts, updates and deletes that it needs to perform in the database. You can also create functions from stored procedures, whether those are to query data or make changes to the database and then call them directly in your application code. You’ve also seen how you can combine the EDM’s ability to use database views and stored procedures to completely circumvent EF’s ability to access database tables directly.With the combination of Entity Framework’s command generation capabilities and it’s broad support for using database stored procedure, you can choose the features that best suit your needs when designing Entity Data Models and planning how to use EF in your applications.
2 comments:
Hello,
I wanted to express my gratitude for sharing this informative blog. Your insights intoOnline learning classes are greatly appreciated. As someone who values education and the evolving learning landscape, I'm intrigued to learn more about the benefits, challenges, and effective strategies associated with online learning classes.
I'm grateful that you produce such excellent content. Are you looking. online tutoring services Using an online tutoring service can boost your grades. From the comfort of your home, set out on a remarkable educational journey! To benefit from the best online tuition services, enroll today! Our large staff of qualified tutors includes several experts in relevant fields. Students can enroll in courses in their native tongue because our educators are drawn from all corners of the nation.
Post a Comment