When it comes to Linq to SQL, there is a very interesting difference between using a stored procedure or a table function (or user defined functions). In this post, I want to discuss the similarities and differences between the two and which one should be used in a given scenario.
Stored Procedures
Stored Procedures can be dragged and dropped on the right hand side of the designer. This will then add the stored procedure as a method on your Data Context. If the stored procedure accepts parameters, then these will be translated into parameters on the method.
The method code for CustOrderHis stored procedure would be:
[Function(Name=”dbo.CustOrderHist”)]
public ISingleResult
{
IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), customerID);
return ((ISingleResult
}
You could then call it in your code:
DataClasses1DataContext dc = new DataClasses1DataContext();
var sproc = dc.CustOrderHist(“ALFKI”);
If we look at the code produced, the first important part is the FunctionAttribute which defines which stored procedure will be called in the database. The next important part is the GetCurrentMethod which allows Linq to access the method to execute. According to the documentation, the method “Returns a MethodBase object representing the currently executing method”. This is then passed into the ExecuteMethodCall which will result in the query being executed against the database.
Once the result has been returned, it is converted into a ISingleResult, which implements IEnumerable, and returned to the calling client. As ISingleResult is a generic class, an object with the same name as the sproc is created with the same properties as returned by the stored procedure.
However, if you know your stored procedure returns a object already in your system, for example GetCustomer might return a row from your Customer table, then you can use that object as the generic type – ISingleResult
When using stored procedures they can only return items of type ISingleResult or IMultipleResult (discussed later). This causes a number of problems when using the created objects. Firstly, in your client code you will have to manually convert the result to something like ToList() if you want to bind the objects to a data source. If you convert the objects to a list then they will not be change-tracked. Finally, you cannot benefit from deferred loading when using stored procedures as the call to the database is executed when the method is called.
If your stored procedure returns two record sets, for example something like below
SELECT * FROM Customers
SELECT * FROM Orders
Then there will be a problem with a method returning ISingleResult. This is where IMultipleResult comes in handy. However just dragging the stored procedure like above will not generate the correct code. Instead, you will have to add your own partial method for the code.
Given this code, we will need to convert it so it can work correctly.
[Function(Name=”dbo.ReturnCustomerAndOrder”)]
public ISingleResult
{
IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())));
return ((ISingleResult
}
The code for it to correctly access two or more different tables would look something like this:
[Function(Name=”dbo.ReturnCustomerAndOrder”)]
[ResultType(typeof(Customer))]
[ResultType(typeof(Order))]
public IMultipleResults ReturnCustomerAndOrder()
{
IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())));
return ((IMultipleResults)(result.ReturnValue));
}
NOTE: Important to include this in a partial class otherwise the designer will override your changes.
To access one of the set of results, you need to explicitly request it using the GetResult<> method.
DataClasses1DataContext dc = new DataClasses1DataContext();
IMultipleResults sproc = dc.ReturnCustomerAndOrder();
List
foreach (Order o in orders)
{
Console.WriteLine(o.OrderID);
}
This allows you to deal with both situations for stored procedures. This is also what you will need to do if your sproc returns a single object but of different types.
User Defined Functions
Like stored procedures, user defined functions can be added as a method on your data context. User defined functions are similar to stored procedures, however you have the ability to defined what is being returned from the function, this allows Linq to correctly understand the sql and produce correct code to match.
The function below returned as table, as such it is called a table valued function.
CREATE FUNCTION [dbo].[AllOrdersInWeek52Function]()
RETURNS TABLE
AS
RETURN (SELECT * FROM Orders
WHERE DATEPART(ww, OrderDate) = 52)
When this code is added to the data context, the following code is produced.
[Function(Name=”dbo.AllOrdersInWeek52Function”, IsComposable=true)]
public IQueryable
{
return this.CreateMethodCallQuery
}
As you can see, this returns an IQueryable object, unlike the sproc which returned ISingleResult. This means that you can take full advantage of Linq when querying against this object in the database.
Other advantages of functions is that they can be executed against the database, inline with an existing linq to sql query. This means that we can do more advanced queries solely in the database without having to bring everything in memory to perform the same task. For example, if we can the function below which takes a date and returns an int representing the week of the year.
CREATE FUNCTION WeekOfYear(@Date DateTime)
RETURNS Int
AS
BEGIN
RETURN (CAST(DATEPART(ww, @Date) AS INT))
END
Then we can use that within our linq query.
DataClasses1DataContext dc = new DataClasses1DataContext();
var q = from o in dc.Orders
where dc.WeekOfYear(o.OrderDate) == 23
select o;
foreach (var item in q)
{
Console.WriteLine(item.OrderID);
}
Now, instead of WeekOfYear being performed in memory over the entire table as would be required without the function then its included within the generatored SQL.
SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight], [t0].[ShipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion], [t0].[ShipPostalCode], [t0].[ShipCountry]
FROM [dbo].[Orders] AS [t0]
WHERE [dbo].[WeekOfYear]([t0].[OrderDate]) = @p0
I’ve highlighted the important section, as you can see this will result in a much better execution.
Summary
In summary, user defined functions provide you with more flexibility and allow you to gain some great advantages over the queries being executed. Very powerful and something to keep in mind. Stored procedures have some limitations, but can solve certain scenarios.
great!!!!!!!
very usefull
How about the perf ? how an sp compares to a function
First off, I am a 12-year DBA veteran of SQL Server and Oracle. This technique may have advantages on the LINQ side, but it is a HORRIBLE idea from the perspective of SQL Server. Table UDF’s create in-memory representation of their return (temp) tables. There is no option to use on-disk temp tables as there are in stored procedures. This would not be a problem for smaller queries, but, as the queries get more complex and load goes up, the performance goes down significantly. I am working on a large portal project right now where one of the first things I did to improve performance was strip out a large number of existing table udf’s and convert them to stored procs using true disk-based temp tables. I can’t begin to tell you the improvement in performance (especially on more complex queries). It was absolutely exponential. I understand how useful this is as a developer, but this is exactly why LINQ still scares me a bit. It lends itself to forgetting about database tuning and, although I am a big LINQ fan for what it can do, I think time will show that it needs some performance maturation.
This is the first nice blog post to deal with this issue.
Thank you –