Friday, August 29, 2008

A small extension to the Entity Framework

After my trials and tribulations with the Entity Framework that I posted last week, I thought I'd dig a little deeper and come up with a solid way to use Stored Procedures through the Entity Framework while maintaining the level of abstraction provided by EF.

You may recall the problem: I have a user account table that stores confidential information that I don't want to directly expose to EF consumers, but I need to occasionally access it (user login, for instance) from the database. I could access it directly through a LINQ to SQL call or something similar, but I was looking for an efficient way to call it using EF.

My first attempts were unsuccessful, as the Entity Wizard is nice enough to identify the stored procedures available from the database and add them to the entity schema, but it only generates methods signatures in the Entity class for procedures that return Entities or are able to map directly to Entities. Granted, this covers most CRUD work inside the db, but it won't cover everything. In my case:

  • The user account, when created, creates a password hash based on a unique salt value and the user's selected password. The hash and the salt values are then stored for any later comparisons, but there's no need for them to exist in any entities that come back to the consumer. A create login procedure, in this instance, doesn't map directly to an Entity (on purpose), but we still need to call the procedure if we want accounts created.

  • In order to re-encrypt the password correctly on each login, the encryption algortihm needs to see the password salt that was entered when the user created the account. I could use a simple text query using the CreateQuery method, but this would expose more of what I'm doing than I'd like. I'd much rather call an encrypted SP that does the processing for me, and returns the result as a string.
The basis for all data connections inside the Entity Framework is a set of classes contained in the System.Data.EntityClient namespace. If you've used the classes contained in the System.Data.Common namespace to execute stored procedures previously, you'll probably be happy to learn that all of the Entity based classes inherit this functionality.

If you make an entity set using a process similar to what I discussed in my last EF post, take a look at the base ObjectContext class created by the designer (it will have the same name as the name of your entity set). This ObjectContext is the manager between the database and the entities themselves. A Connection property in the ObjectContext exposes all we need to add some extension methods that will enable us to run non-entity based procedures while still maintaining the abstraction provided by the Entity Framework.

Since our Entity data functionality extends System.Data.Common, I've got all I need:

  1. Expose the EntityConnection using the ObjectContext.Connection.
  2. The connection can run any function created in our Entity using the CreateCommand method of the EntityConnection.
  3. In order to access our stored procedures, we need to access them by using our Connection's store name, as well as the name of the imported function in the format StoreName.FunctionName.
  4. The EntityCommand.CommandType must be set to CommandType.StoredProcedure.
  5. We need to either accept the passed in parameters (given that they're EntityParameters), convert them if they're DbParameters, or otherwise handle them.
  6. The passed parameters need to be added to the command.
  7. The command is then ready for the appropriate execution type.
EntityCommand and DbCommand both have several different methods attached based on the type of stored procedure: my solution for this was to build a method that creates the EntityCommand (I called it CreateStoreCommand in the provided code sample), then run the correct method based on the procedure itself.

  1. private static EntityCommand CreateStoreCommand(ObjectContext context, string functionName, object[] parameters)
  2. {
  3. EntityConnection entityConnection = (EntityConnection)context.Connection;
  4. string commandName = string.Format("{0}.{1}",context.DefaultContainerName,functionName);
  5. EntityCommand entityCommand = entityConnection.CreateCommand();
  6. entityCommand.CommandType = System.Data.CommandType.StoredProcedure;
  7. entityCommand.CommandText = commandName;
  8. if (null != parameters && parameters.Length > 0)
  9. {
  10. EntityParameter[] dbParams = new EntityParameter[parameters.Length];
  11. if (parameters is EntityParameter[])
  12. {
  13. ((EntityParameter[])parameters).CopyTo(dbParams, 0);
  14. }
  15. else
  16. {
  17. for (int i = 0; i < parameters.Length; i++)
  18. {
  19. EntityParameter p = null;
  20. object param = parameters[i];
  21. if (param is DbParameter)
  22. {
  23. DbParameter parm = (DbParameter)param;
  24. p = (EntityParameter)parm;
  25. }
  26. else if (param is ObjectParameter)
  27. {
  28. p = entityCommand.CreateParameter();
  29. ObjectParameter o = (ObjectParameter)param;
  30. p.ParameterName = (o.Name.StartsWith("@")) ? o.Name.TrimStart(new char[] {'@'}) : o.Name;
  31. p.Value = o.Value;
  32. }
  33. else
  34. {
  35. p = entityCommand.CreateParameter();
  36. p.Value = parameters[i];
  37. }
  38. dbParams[i] = p;
  39. }
  40. entityCommand.Parameters.AddRange(dbParams);
  41. }
  42. }
  43. return entityCommand;
  44. }

To utilize our EntityCommand, we'll build some extension methods for ObjectContext. If you're not familiar with extension methods, C# 3.5 allows us to extend prebuilt classes with custom code, and we can easily reuse this functionality by simply including this class in our projects. These functions are public static methods with a first parameter specified by the keyword this. For instance, if I want to extend the ObjectContext class directly to execute a scalar function from my database using a pre-existing stored procedure, I need to create a method like so:

  1. public static object ExecuteScalar(this ObjectContext context, string functionName, params object[] parameters)
  2. {
  3. if (context == null) return null;
  4. EntityCommand command = CreateStoreCommand(context, functionName, parameters);
  5. return command.ExecuteScalar();
  6. }

I've got my scalar method. Note that the first parameter uses this to specify that this is an Extension method: if I include a reference to this class (or have the class in my namespace), any ObjectContext references will be able to execure these extensions:

If you look at the tool tip in the photo above, you should be able to see that I can now cleanly access any function that I add to the EntitySet that I create. Since we're looking to hide the underlying data specifics (we leave the messy details to the .edmx file), this is intended behavior.

I'll be posting a link to the TVUG site shortly for registered users, and I'll post a copy here when I'm able to.