Click here to Skip to main content
Click here to Skip to main content

Code First Stored Procedures

By , 13 Nov 2012
 

Introduction

Code First is a new, lightweight database interface provided by the same team that brought you MVC3 and Razor. While it does many things well, one hole in its interface is Stored Procedures. This project provides routines that can call Stored Procedures and will properly handle input and output parameters, return codes, and multiple result sets.

Background

The problem with calling Stored Procedures from a strongly typed language is that they're messy. They can accept data either through scalar parameters or a table (T-SQL), and can return data via their parameters, by a return code and through multiple result sets, each of which may be completely different. Until the very clever people at Microsoft add Stored Procedure support to Code First (Entity Framework 4.1), you can use this interface routine. This project provides classes and Extension Methods to allow you to call Stored Procedures and deal with all that messiness in a relatively clean way.

Using the code

Let's begin with some sample Stored Procedures:

//
-- Stored procedure with input and output parameters, and a single result set
create proc testone @in varchar(5), @out int out
as
begin
    select table_name, column_name from INFORMATION_SCHEMA.COLUMNS
    set @out = @@ROWCOUNT
end
go

-- Stored procedure with no parameters, a return code and a single result set
create proc testtwo
as
begin
    select TABLE_CATALOG, TABLE_NAME
    from INFORMATION_SCHEMA.TABLES
    return @@ROWCOUNT
end
go

-- Stored procedure with no parameters and multiple result sets
create proc testthree
as 
begin
    select table_name, column_name from INFORMATION_SCHEMA.COLUMNS
    select TABLE_CATALOG, TABLE_NAME
    from INFORMATION_SCHEMA.TABLES
end
go

These three example procedures do most of the messy things that we expect from Stored Procedures: input and output parameters, return codes, and multiple result sets. It's probably important to note that any one Stored Procedure can do all of these to return data to the caller.

To use this code, we'll need to follow a fairly straightforward calling pattern: create classes for parameters and result sets, populate the parameters class, call the Extension Method, and then process the outputs. Here's the example classes for the first Stored Procedure above:

/// <summary>
/// Parameters object for the 'testoneproc' stored procedure
/// </summary>
public class testone
{
    // Override the parameter name. The parameter name is "in",
    // but that's not a valid property
    // name in C#, so we must name the property
    // something else and provide an override to set 
    // the parameter name.
    [StoredProcAttributes.Name("in")]
    [StoredProcAttributes.ParameterType(System.Data.SqlDbType.VarChar)]
    public String inparm { get; set; }

    // This time we not only override the parameter name, we're also setting the parameter
    // direction, indicating that this property will only receive data, not provide data
    // to the stored procedure. Note that we must include the size in bytes.
    [StoredProcAttributes.Name("out")]
    [StoredProcAttributes.Direction(System.Data.ParameterDirection.Output)]
    [StoredProcAttributes.Size(4)]
    public Int32 outparm { get; set; }
}

/// <summary>
/// Results object for the 'testoneproc' stored procedure
/// </summary>
public class TestOneResultSet
{
    [StoredProcAttributes.Name("table_name")]
    public string table { get; set; }

    [StoredProcAttributes.Name("column_name")]
    public string column { get; set; }
}

To process the output parameter, we decorate the appropriate property with the Direction attribute and give it the value ParameterDirection.Output. When the call to the Stored Procedure returns, this will automatically hold the output value set within the Stored Procedure. We also set the Size parameter to 4 bytes to match the size of the integer return value. If the Size parameter is too small, your returned values will be truncated. Using these classes, we can now define a StoredProc object which defines the Stored Procedure.

// simple stored proc
public StoredProc<testone> testoneproc = 
       new StoredProc<testone>(typeof(TestOneResultSet));

The definition of the Stored Procedure contains the parameters class, and the constructor contains each of the expected return set types, in the same order they are created within the Stored Procedure. In this case, we're expecting one return set so we provide one type object. It's important to note that any type provided as a result set type must have a default constructor, that is a constructor that takes no parameters. Now that we have the data source and destination classes and our Stored Procedure defined, we can make the call to the database.

using (testentities te = new testentities())
{
    //-------------------------------------------------------------
    // Simple stored proc
    //-------------------------------------------------------------
    var parms1 = new testone() { inparm = "abcd" };
    var results1 = te.CallStoredProc<testone>(te.testoneproc, parms1);
    var r1 = results1.ToList<TestOneResultSet>();
}

Note that the names of the parameters in the parameters class should match the names of the parameters declared in the Stored Procedure definition. If this is not possible (as in our example above: "in" and "out" are not valid property names), then the Name attribute can be used to override the default and specify the parameter name.

In keeping with the Code First philosophy of using lightweight POCO objects as data carriers, result set values are copied into the output object by matching the name of the column in the result set with the name of the property in the return type. This 'copy by property' is sensitive to the NotMappedAttribute used to identify object properties that should not be mapped to database I/O, and can be overridden using the Name attribute.

The ToList<T> accessor method in the Result Set object will search for the result set containing objects of that particular type and return the first one found, casting it to a List of the correct type. The data returned in the output parameter "out" defined in the Stored Procedure is automatically routed back to the mapped property ("outparm") in the parameters object.

The second example Stored Procedure has both a result set and a return code. To process the return code, we could create a parameters class and decorate the property with the Direction attribute and give it the value ParameterDirection.ReturnValue. When the call to the Stored Procedure returns, this will hold the return code set within the Stored Procedure. Note that in SQL Server, this must be an integer value. If, for whatever reason, you wished to ignore the return code parameter, you can call the non-generic version of CallStoredProc:

// stored proc with no parameters
public StoredProc testtwo = new StoredProc("testtwo", typeof(TestTwoResultSet));

//-------------------------------------------------------------
// Simple stored proc with no parameters
//-------------------------------------------------------------
var results2 = te.CallStoredProc(testtwo);
var r2 = results2.ToList<TestTwoResultSet>();

In this case, we're intentionally discarding the return code parameter that will be returned by the Stored Procedure. This does not cause an error. It's also possible to ignore result sets. The CallStoredProc routine will only save result sets for which a type was specified in the method call. Conversely, it will not cause an error if the Stored Procedure returns fewer result sets than you provide types for.

The third example returns multiple result sets. Since this Stored Procedure does not accept parameters or return a return code, the set up is simple - just call the procedure. In this case, we're using Fluent API style methods to assign values to the StoredProc object.

StoredProc testthree = new StoredProc()
    .HasName("testthree")
    .ReturnsTypes(typeof(TestOneResultSet), typeof(TestTwoResultSet));

//-------------------------------------------------------------
// Stored proc with no parameters and multiple result sets
//-------------------------------------------------------------
var results3 = te.CallStoredProc(testthree);
var r3_one = results3.ToList<TestOneResultSet>();
var r3_two = results3.ToArray<TestTwoResultSet>();

The ToList<T>() method of the ResultsList searches for the first result set containing the indicated type, so we can simplify accessing the return values by specifying the type we want and let the ResultsList figure out the right result set for us. If the result set could not be found, an empty list of the correct type is returned, so the return from ToList swill never be null. If your Stored Procedure returns more than one instance of the same result set, the ToList method will return the first result set. You can use the array indexer [] or create an enumerator over the ResultsList to process all the result sets.

Table Valued Parameters

SQL Server can accept a table as a parameter to a Stored Procedure. In the database, we need to create a user type for the table and then declare the Stored Procedure parameter using this type. The 'Readonly' modifier on the parameter is required.

-- Create Table variable 
create type [dbo].[testTVP] AS TABLE(
 [testowner] [nvarchar] (50) not null, 
 [testtable] [nvarchar] (50) NULL,
 [testcolumn] [nvarchar](50) NULL
)
GO

-- Create procedure using table variable
create proc testfour @tt testTVP readonly
as
begin
    select table_schema, table_name, column_name from INFORMATION_SCHEMA.COLUMNS
    inner join @tt
    on table_schema = testowner
    where (testtable is null or testtable = table_name)
    and (testcolumn is null or testcolumn = column_name)
end
go

On the .NET side of things, we need to create a class to represent rows in this table, and we need to duplicate the table definition so that the data rows can be processed appropriately. We will create a class to represent the rows of the table being passed to the table valued parameter, and decorate that class with attributes that match the SQL table definition.

/// <summary>
/// Class representing a row of data for a table valued parameter.
/// Property names (or Name attribute) must match table type column names
/// </summary>
[StoredProcAttributes.Schema("dbo")]
[StoredProcAttributes.TableName("testTVP")]
public class sample
{
    [StoredProcAttributes.Name("testowner")]
    [StoredProcAttributes.ParameterType(SqlDbType.VarChar)]
    [StoredProcAttributes.Size(50)]
    public string owner { get; set; }

    [StoredProcAttributes.ParameterType(SqlDbType.VarChar)]
    [StoredProcAttributes.Size(50)]
    public string testtable { get; set; }

    [StoredProcAttributes.ParameterType(SqlDbType.VarChar)]
    [StoredProcAttributes.Size(50)]
    public string testcolumn { get; set; }
}

The Schema and TableName attributes identify the table definition we're passing to the table valued parameter, and the methods matching each column must be decorated with the appropriate SQL column definition attributes.

/// <summary>
/// Parameter object for 'testfour' stored procedure
/// </summary>
public class testfour
{
    [StoredProcAttributes.ParameterType(SqlDbType.Structured)]
    [StoredProcAttributes.Name("tt")]
    public List<sample> tabledata { get; set; }
}


/// <summary>
/// output class for proc test four
/// </summary>
public class TestFourResultSet
{
    public string table_schema { get; set; }
    public string table_name { get; set; }
    public string column_name { get; set; }
}

Now that we have a class matching our table definition, we can create an instance of a table valued parameter in the parameters object. Create a property with an IEnumerable<> type and give it the SqlDbType Structured. The CodeFirstStoredProc routines will build the table definition dynamically and pass the table to the Stored P. To use, simply give the table valued parameter a list or array of the underlying class type.

//-------------------------------------------------------------
// Stored proc with a table valued parameter
//-------------------------------------------------------------

// new parameters object for testfour
testfour four = new testfour();

// load data to send in the table valued parameter 
four.tabledata = new List<sample>()
{
    new sample() { owner = "tester" },
    new sample() { owner = "dbo" }
};

// call stored proc
var ret4 = te.CallStoredProc<testfour>(te.testfour, four);
var retdata = ret4.ToList<TestFourResultSet>();

Acknowledgements

Thanks and acknowledgements to everyone from whom I've learned these techniques, all those bloggers who took the time to post tips that have helped my knowledge, and with a special mention to StackOverflow, without which this could not exist.

History

  • Version 1.0: Initial release.
  • Version 2.0
    • Breaking changes!
    • Complete replacement of the interface, making this much simpler to use and much more in line with the Code First style.
  • Version 2.1
    • Breaking changes! Yes, it's a bad habit, but in this case it's a good thing.
    • Removal of the "TableType" class and replacing it with the simpler and more "Code First" style of defining a table valued parameter through POCO objects and attributes.
    • Added precision and scale attributes.
  • Version 2.5
    • Updated to support EF 5 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

aureolin
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionCould not load type 'System.ComponentModel.DataAnnotations.NotMappedAttribute'memberaureolin1 Apr '13 - 14:06 
A user ran into the error recently. This is caused by some of the changes MS did between EF 4.x and EF 5.0; nothing like breaking changes to make your life interesting! If you receive this error, make sure you are using the most recent version of the CodeFirstStoredProcs.dll. The file will report version 2.5, but visual studio still thinks it's version 2.2. (Something else to fix in the next version!)
 
Steve G.
QuestionSample ProjectmemberEnrique De la Castillo29 Mar '13 - 13:35 
Does anyone have a Basic Sample project of this article. I can create the stored procedure myself I am just stuck on how and where to create the classes in an MVC 4 Project.
 

Thank you in advance
AnswerRe: Sample Projectmemberaureolin30 Mar '13 - 11:51 
The samples in the article are the actual working code out of my sample program. As for creating classes in an MVC project, I'd recommend putting classes that interact with stored procedures in the "Models" directory.
GeneralBrilliant WorkmemberAlex B. Clarke16 Feb '13 - 7:06 
Excellent work! Thank you.
Alex B. Clarke
It's easy when you know it.

QuestionNice post.memberRohit Kesharwani29 Dec '12 - 8:22 
How to define <b>te.testfour</b> procedure?
I have defined prodecure in context class like below:
public StoredProc<testone> testoneproc = new StoredProc<testone>(typeof(TestOneResultSet));
public StoredProc<testtwo> testwoproc = new StoredProc<testtwo>(typeof(TestTwoResultSet));
public StoredProc testthree = new StoredProc().HasName("testthree").ReturnsTypes(typeof(TestOneResultSet), typeof(TestTwoResultSet));
 
But I am confused while declaring for test four.
public StoredProc testfour = ?
 
Please help me.
AnswerRe: Nice post.memberaureolin31 Dec '12 - 6:01 
The definition of the test four procedure follows the same patterns as the previous declarations. You can declare the test four proc as a property of a class as:
        public StoredProc<testfourdata> testfourproc { get; set; }
and initialize it as
            testfourproc = new StoredProc<testfourdata>()
                .ReturnsTypes(typeof(TestFourResultSet));
Hope this helps!
Steve G.
GeneralRe: Nice post.memberRohit Kesharwani1 Jan '13 - 6:16 
Thanks. its working.
very useful post.
GeneralRe: Nice post.memberRohit Kesharwani1 Jan '13 - 6:50 
Again I have one question, please provide some guide related to that so that I can move forward:
I have created a Model shown below:
      public class Category
      {
            public int Id { get; set; }
            public string Name { get; set; }
 
            public virtual ICollection<Product> Products { get; set; }
      }
 
      public class Product
      {
            public int Id { get; set; }
            public string Name { get; set; }
 
            public virtual Category Category { get; set; }
      }
 
      public class ProductContext : DbContext
      {
            public StoredProc productproc = new StoredProc().HasName("sp_GetProductList").ReturnsTypes(typeof(Category), typeof(Product));
      }
 
Stored procedure in my database:
 
create procedure [dbo].[sp_GetProductList]
as
begin
     select * from categories
     select * from products
end
go
 
Calling procedure from Controller:
 
ProductContext productDb = new ProductContext();
var results3 = productDb.CallStoredProc(productDb.productproc);
var r3_one = results3.ToList<Category>();
var r3_two = results3.ToArray<Product>();
 
Everything is good, but the problem is that the property Products in Product model and Category in Category model is null, while I retreive data through procedure. How can I manage it through procedure?
It will worked fine when I use Dbset:
public class ProductContext : DbContext
{
            public DbSet<Category> Categories { get; set; }
            public DbSet<Product> Products { get; set; }
}
 
Please provide me some trick or logic or any automatic way of doing this.
Any idea, suggestion would be appreciated.
Thanks.
QuestionWhere is the body of the procedure defined?memberMember 968037912 Dec '12 - 11:49 
One thing I don't understand from this
tutorial, I see where the parameters passed are defined,
but I do not see where the body of the procedure
is defined. Could you please give specifics on
how I would define the body of the procedure to work
with the code given in this tutorial?
AnswerRe: Where is the body of the procedure defined?memberaureolin12 Dec '12 - 12:15 
The body of the stored procedures called using this library are stored in your database, most likely created by your DBA.
 
Steve G.
GeneralRe: Where is the body of the procedure defined?memberMember 968037913 Dec '12 - 3:28 
Does that mean that you have to create the body in the database directly using an SQL
query or the SQL db manager before it can be accessed in code? Isn't there a way to create
the body in code first on the .NET side of things, i.e. - create the entire procedure in
code first instead of in SQL/SQL db manager? Or is that beyond the scope of this article? I have been looking for how to do this, and could not find an answer on google, nor did I get a helpful reply on MSDN forums.
QuestionTestTwoResultSetmemberaz_coder5 Dec '12 - 13:21 
Very nice post. I have a question. Do you have an example of the TestTwoResultSet? I have a stored procedure and I simply want the return code. I am using the below model, but it is not populating the return value. I have also tried making the ReturnCode an int.
 
public class BigIntReturnCode
{
    [StoredProcAttributes.Name("ReturnCode")]
    [StoredProcAttributes.Direction(System.Data.ParameterDirection.ReturnValue)]
    [StoredProcAttributes.Size(8)]
    public Int64 ReturnCode { get; set; }
}
 
Thanks.
AnswerRe: TestTwoResultSetmemberaureolin6 Dec '12 - 5:14 
Here's how to get the return code. I've modified my "Testtwo" example to retrieve the returncode. While the testtwo stored proc has no parameters, we must pass our return code variable as a parameter to the database call to retrieve the return code.
 
Definitions:
    // define a parameter to hold the return code
    public class testtwo
    {
        [StoredProcAttributes.Name("ReturnCode")]
        [StoredProcAttributes.Direction(System.Data.ParameterDirection.ReturnValue)]
        [StoredProcAttributes.Size(8)]
        public Int64 ReturnCode { get; set; }
    }
 
and define the call as having a parameter object
 
    // stored proc with no parameters, but retrieves the return code
    public StoredProc<testtwo> testtwo = new StoredProc<testtwo>(typeof(TestTwoResultSet));
 
 
Here's the call to the database
    //-------------------------------------------------------------
    // Simple stored proc with a parameter for the return code
    //-------------------------------------------------------------
    var testtwodata = new testtwo();
    var results2 = te.CallStoredProc<testtwo>(te.testtwo, testtwodata);
    var r2 = results2.ToList<TestTwoResultSet>();
 
After the call, testtwodata.ReturnCode will have the stored procedures returncode value.
 
Hope this helps!
 
Steve G.
GeneralRe: TestTwoResultSetmemberaz_coder6 Dec '12 - 8:58 
Awesome. I added the return parameter to my insert parameter class and it worked like a charm.
Thank you so much for your quick response.
GeneralMy vote of 5 [modified]membersanndeb16 Nov '12 - 20:16 
very simple & good one... exactly what i am looking for.

modified 17 Nov '12 - 2:22.

GeneralMy vote of 4memberravithejag13 Nov '12 - 16:39 
nice
QuestionNice example but incompletememberPDeibel21 Sep '12 - 6:54 
Your code is simple and elegant but there are some newbies out there that their first encounter with this subject may be a legacy stored procedure to do some processing (ie. one that has 0,1, or more input values and no output values) and you don't provide an example for them that doesn't return any (scalars or tables).
 
You may want to consider that for your next release.
AnswerRe: Nice example but incompletememberaureolin21 Sep '12 - 10:17 
You are correct. I've made the assumption that someone who needs to code an interface to a stored procedure knows something about working with stored procedures. I'll look into this suggestion, I think it's a good one.
 
Steve G.
QuestionSilverlightmemberMrTouya6 Apr '12 - 14:01 
Can you create a Silverlight compatible DLL? Smile | :)
 
Thanks,
 
Stephane
AnswerRe: Silverlightmemberaureolin9 Apr '12 - 7:46 
Can I ask how the current .Dll is not Silverlight compatible?? I'm really not familiar with Silverlight and don't know if there are any special things that need to be done in the called .Dll for Silverlight ...
 
Steve G.
QuestionAlmost what I neededmemberOffApps7 Mar '12 - 5:38 
Great work. I added this to a project that I am working on, and it functions. It is a bit slow however. I am returning about 1500 rows in 4 return sets (4 types) and it takes about 2 minutes to resolve those into lists.
 
For the time being I will have to go with some other solution, but I would be very interested to get into CallStoredProc class and results class and see if I can help optimize them some.
AnswerRe: Almost what I needed [modified]memberaureolin7 Mar '12 - 5:53 
Hmm... That's nearly 6 seconds a row. How many fields are you returning per row? My testing showed nothing like this sort of really slow performance, and I've processed some fairly large datasets.
 
To gain a little perspective, I routinely query 2500 records from my database in about a half a second, and that includes going to the database and executing my stored proc as well as processing the returned data.
 
Steve G.

modified 9 Mar '12 - 17:13.

Questionnice work! I think...membermaximus.dec.meridius21 Oct '11 - 8:36 
I think this will help fix the issue I was having with my code below:
 
private static IEnumerable GetAllFullWindowsStats()
{
UpgradeDataContext db = new UpgradeDataContext();
 
//return db.Database.SqlQuery("exec sproc_getFullWindowStats").ToList();
return db.Database.SqlQuery("exec sproc_getFullWindowStats");
 
//Error: The type arguments for method 'System.Data.Entity.Database.SqlQuery(string, params object[])' cannot be inferred from the usage. Try specifying the type arguments explicitly.
 
}
 
I havent ever figured out why I get that error. Beginner here but really liked your work.
 
Thanks
AnswerRe: nice work! I think...memberaureolin21 Oct '11 - 10:37 
Thanks, this project will help make your life a little easier.
 
To answer your implied question, the SQLQuery method takes two parameters, and you only gave it one. Your call should have looked something like:
 
return db.Database.SqlQuery("exec sproc_getFullWindowStats", new object[]);
 
Good Luck,
Steve G.
GeneralRe: nice work! I think...membermaximus.dec.meridius21 Oct '11 - 13:16 
the answer was :
 
return db.Database.SqlQuery("exec sproc_getFullWindowStats");
 
keep improving your project...lovin it!
 
Thank you -- voted 5 stars!!!
GeneralMy vote of 5memberJoshua Lunsford14 Oct '11 - 3:04 
This works! amazing!!!
Questionnamespace/dll to add for StoredProcAttributes attributememberSagar Singh Biswakarma18 Sep '11 - 6:46 
Hey mite,
 
Nice article.Can you pls let us know which namespace are you using for
StoredProcAttributes.Which dll need to be referenced to add the required namespace?
 
Thanks in adv.
AnswerRe: namespace/dll to add for StoredProcAttributes attributememberaureolin18 Sep '11 - 7:04 
"CodeFirstStoredProcs" is the namespace that I'm using for this project.
 
Steve G.
GeneralMy vote of 5membercareced26 Jul '11 - 3:31 
Nice Article!
GeneralMy vote of 4membersmimon18 Jul '11 - 1:53 
Good solution, however I feel the documentation could be a presented in a little more clear, step by step fashion. Also, too much usage of "var"!
GeneralMy vote of 5membermaster vicky30 Jun '11 - 21:17 
Nice article
GeneralMy vote of 5memberjayantbramhankar30 Jun '11 - 20:12 
Good
GeneralMy vote of 5memberCS140130 Jun '11 - 19:55 
super
GeneralFirst Time I have seen fit to write a comment on code project vote of 5memberMember 409404726 May '11 - 16:56 
Absolutely awesome
Your method is sound.
I was surprised it worked with TimeStamp columns havent had a chance to test varbinary but I am pretty sure it will work.
Looking to use this to return flat viewmodels...
awesome work
GeneralMy vote of 5memberMBigglesworth7917 May '11 - 2:06 
Very useful article!
Generalgood!memberMubi | www.mrmubi.com16 May '11 - 22:45 
good work!
Mubshir Raza Ali
^^^^^^^^^^^^^^^^^^^^
http://www.mrmubi.com

GeneralMy vote of 5memberlinuxjr25 Apr '11 - 14:41 
Thank you for sharing your work
GeneralNice workmvpSacha Barber10 Apr '11 - 20:18 
Pretty useful
Sacha Barber
  • Microsoft Visual C# MVP 2008-2011
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralMy vote of 5memberDrABELL9 Apr '11 - 12:34 
Interesting solution, and rather simple coding. 5*

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 13 Nov 2012
Article Copyright 2011 by aureolin
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid