By: Artemakis Artemiou | Updated: 2018-12-13 | Comments (4) | Related: > Application Development
Problem
In previous .NET Application Development tips, we've learned how to get started with .NET and SQL Server data access. To this end, we've learned how to connect to SQL Server from a C# program and run a simple query, as well as how to query SQL Server tables from .NET and process the results. Moreover, we've learned how to work with SQL Server stored procedures from within .NET applications. In this tip, we continue this journey and we will learn how to work with SQL Server functions from within a .NET Application.
Solution
There is more than one type of SQL Server function. Currently, in SQL Server 2017, the function types are:
- Table-valued Functions
- Scalar-valued Functions
- Aggregate Functions
- System Functions
In this tip, we are going to see 3 different examples of calling 3 of the above types of SQL Server functions from within our .NET application. Note that we won't examine Aggregate Functions, because we would need to create a CLR assembly for this, which is something that will be covered in a future tip.
Sample Database
Just like in my previous tips, all examples will be based on the database "SampleDB" which can be found on a test SQL Server 2017 named instance on my local machine, which is called "SQL2K17".
Here's a screenshot of the SQL Server instance, as it can be seen in SSMS:
The sample database has two tables named "employees" and "location", as well as the below two user-defined functions:
- fnGetEmployeeInfo (table-valued function)
- fnGetTotalEmployees (scalar-valued function)
Let's take a look at their DDL script:
Function "fnGetEmployeeInfo" DDL Script:
USE [SampleDB] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE FUNCTION [dbo].[fnGetEmployeeInfo] ( @empID INT ) RETURNS TABLE AS RETURN ( SELECT e.id, e.code, e.firstName, e.lastName, e.locationID, l.code AS locationCode, l.descr AS localDescr FROM dbo.employees e INNER JOIN dbo.location l ON l.id = e.locationID WHERE e.id=@empID ); GO
Function "fnGetTotalEmployees" DDL Script:
USE [SampleDB] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE FUNCTION [dbo].[fnGetTotalEmployees] ( @locationID INT ) RETURNS INT AS BEGIN DECLARE @result INT SELECT @result=(SELECT COUNT(*) FROM dbo.employees WHERE locationID=@locationID); -- Return the result of the function RETURN @result END GO
The fnGetEmployeeInfo function, takes as an input parameter the employee ID and returns employee-related information.
The fnGetTotalEmployees function, takes an input parameter the location ID and returns the number of all employees for the specific location.
We will use the above two functions in our examples, as well as the system function "GETDATE()".
The system function "GETDATE()", as the name implies, returns the current date and time.
Sample Data
The sample database has two tables named "employees" and "location". In the below screenshot, you can take a look at the data currently stored in these two tables.
Calling the SQL Server Functions from a .NET Application
The next step in our example, is to write a .NET application, preferable in C#, that connects to the database server and properly calls the 3 functions.
This example, is based on the examples originally presented in my previous .NET Application Development tips.
One of the main points, again like in the case of stored procedures, is that I will make use of the .NET Class SqlParameter in order to write more secure code, thus minimizing the risk for SQL injections.
Connecting to the SQL Server Instance – Connection String
This is my connection string:
string connString = @"Server =.\SQL2K17; Database = SampleDB; Trusted_Connection = True;";
Using the above connection string, I will connect to the named instance "SQL2K17" on the local machine, using a trusted connection, that is, with my windows account.
Calling SQL Server Function "fnGetEmployeeInfo"
In the below code example, I'm presenting the full .NET code for a C# console application that calls the function "fnGetEmployeeInfo" and process the results.
Note: Prior to start writing the code, similarly to my previous tips on .NET Application Development, you will need to create a new Visual C# project in Visual Studio, and select the "Console App (.NET Framework)" template. In my example, I named the project " TestApp4" and saved it in the "C:\temp\demos" folder on my local machine (a new subfolder with the name of the project was created).
Here's the code:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.SqlClient; using System.Data; namespace TestApp4 { class Program { static void Main(string[] args) { //set the connection string string connString = @"Server =.\SQL2K17; Database = SampleDB; Trusted_Connection = True;"; //variables to store the query results int empID, locationID; string empCode, empFirstName, empLastName, locationCode, locationDescr; try { //sql connection object using (SqlConnection conn = new SqlConnection(connString)) { //define the query text string query = @"SELECT * FROM [dbo].[fnGetEmployeeInfo](@empID);"; //define the SqlCommand object SqlCommand cmd = new SqlCommand(query, conn); //parameter value will be set from command line SqlParameter param1 = new SqlParameter(); param1.ParameterName = "@empID"; param1.SqlDbType = SqlDbType.Int; param1.Value = int.Parse(args[0].ToString()); //pass parameter to the SQL Command cmd.Parameters.Add(param1); //open connection conn.Open(); //execute the SQLCommand SqlDataReader dr = cmd.ExecuteReader(); Console.WriteLine(Environment.NewLine + "Retrieving data from database..." + Environment.NewLine); Console.WriteLine("Retrieved records:"); //check if there are records if (dr.HasRows) { while (dr.Read()) { empID = dr.GetInt32(0); empCode = dr.GetString(1); empFirstName = dr.GetString(2); empLastName = dr.GetString(3); locationID = dr.GetInt32(4); locationCode = dr.GetString(5); locationDescr = dr.GetString(6); //display retrieved record Console.WriteLine("{0},{1},{2},{3},{4},{5},{6}", empID.ToString(), empCode, empFirstName, empLastName, locationID, locationCode, locationDescr); } } else { Console.WriteLine("No data found."); } //close data reader dr.Close(); //close connection conn.Close(); } } catch (Exception ex) { //display error message Console.WriteLine("Exception: " + ex.Message); } } } }
Explanation
As you can see in the above code, the main query text is:
string query = @"SELECT * FROM [dbo].[fnGetEmployeeInfo](@empID);";
So, what I actually do in the above code, is that I'm treating the function "fnGetEmployeeInfo" like a table, because it is a table-valued function and returns a table. Moreover, again, for safer code, I'm using a SqlParameter object.
Right after you finish writing the code, you will need to compile and run the program. To do this, within our project in Visual Studio, by pressing the key F6 or by clicking on the "Build" menu and then click on "Build Solution", our program will be compiled and if everything is OK, that is if we get no errors and see the "Build succeeded" notification on the bottom left corner of the window, it means that the program is now ready for execution.
To execute the program, since this is a console application, we need to navigate to the project's directory and run the executable. In this demo, the project directory is "C:\temp\demos\TestApp4". Therefore, from the command prompt, we need to navigate to "C:\temp\demos\TestApp4\TestApp4\bin\Debug" and execute the "TestApp4.exe" program (or if you changed the assembly name, run the proper executable file that is generated upon compiling the program) along with passing the parameter value for the employee ID.
For example, I'm running the below command based on which I'm requesting information for employee with ID=2:
C:\temp\demos\TestApp4\TestApp4\bin\Debug>TestApp4.exe 2
Here's the output of the program's execution:
Calling SQL Server Function "fnGetTotalEmployees"
Now let's modify our code in order to call the scalar function "fnGetTotalEmployees". This function takes location ID as an input parameter and returns all employees that have the same location ID.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.SqlClient; using System.Data; namespace TestApp4 { class Program { static void Main(string[] args) { //set the connection string string connString = @"Server =.\SQL2K17; Database = SampleDB; Trusted_Connection = True;"; try { //sql connection object using (SqlConnection conn = new SqlConnection(connString)) { //define the query text string query = @"SELECT [dbo].[fnGetTotalEmployees](@empID) AS TotalEmployees;"; //define the SqlCommand object SqlCommand cmd = new SqlCommand(query, conn); //parameter value will be set from command line SqlParameter param1 = new SqlParameter(); param1.ParameterName = "@empID"; param1.SqlDbType = SqlDbType.Int; param1.Value = int.Parse(args[0].ToString()); //pass parameter to the SQL Command cmd.Parameters.Add(param1); //open connection conn.Open(); //execute the SQLCommand Int32 functionResult = (Int32)cmd.ExecuteScalar(); Console.WriteLine(Environment.NewLine + "Retrieving data from database..." + Environment.NewLine); Console.WriteLine("Retrieved result:"); //display retrieved result Console.WriteLine("Total employees for location id={0}: {1}", args[0].ToString(), functionResult.ToString()); //close connection conn.Close(); } } catch (Exception ex) { //display error message Console.WriteLine("Exception: " + ex.Message); } } } }
Explanation
As you can see in the above code, the main query text is:
string query = @"SELECT [dbo].[fnGetTotalEmployees](@empID) AS TotalEmployees;";
This query is totally different than the first example, because now we are calling a scalar-valued SQL function, that is a function that just returns one result.
Again, I'm using a parameter, but as you can see in the code, there is no need to use a SqlDataReader object because scalar-valued functions return a single value. Therefore, I have only converted the value to an Int32 data type.
OK, let's compile and run the new code and request the total number of employees for location id 2.
C:\temp\demos\TestApp4\TestApp4\bin\Debug>TestApp4.exe 2
Here's the output of the program's execution:
Let's run again for location 1:
C:\temp\demos\TestApp4\TestApp4\bin\Debug>TestApp4.exe 1
Calling SQL Server System Function "GETDATE()"
Our last example, is to call one SQL Server system function. In this example, we are calling "GETDATE()" which returns the current date and time.
Here's how our code changes:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.SqlClient; using System.Data; namespace TestApp4 { class Program { static void Main(string[] args) { //set the connection string string connString = @"Server =.\SQL2K17; Database = SampleDB; Trusted_Connection = True;"; try { //sql connection object using (SqlConnection conn = new SqlConnection(connString)) { //define the query text string query = @"SELECT GETDATE() AS CurrentDateTime;"; //define the SqlCommand object SqlCommand cmd = new SqlCommand(query, conn); //open connection conn.Open(); //execute the SQLCommand DateTime functionResult = (DateTime)cmd.ExecuteScalar(); Console.WriteLine(Environment.NewLine + "Retrieving data from database..." + Environment.NewLine); Console.WriteLine("Retrieved result:"); //display retrieved result Console.WriteLine("Current DateTime Value: {0}", functionResult.ToString()); //close connection conn.Close(); } } catch (Exception ex) { //display error message Console.WriteLine("Exception: " + ex.Message); } } } }
Explanation
As you can see in the above code, the main query text is:
string query = @"SELECT GETDATE() AS CurrentDateTime;";
This query is similar to the previous one where we called the scalar-valued function "fnGetTotalEmployees". Again, it returns just one value. The only difference is the data type which requires us to cast the function's result to the DateTime type.
We compile again and run the program and this is the output:
Conclusion
In this tip, we discussed how you can work with different types of SQL Server functions from within .NET applications. To this end, we worked with table-valued and scalar-valued SQL Server functions using C# code, as well as with the SQL Server system function GETDATE().
Stay tuned, we will be continuing this interesting series of tips on SQL Server and .NET.
Next Steps
- Check out my tip: How to Get Started with SQL Server and .NET
- Check out my tip: Querying SQL Server Tables from .NET
- Check out my tip: Working with SQL Server Stored Procedures and .NET
- Check out my tip: Understanding SQL Server Connection Pooling in ADO.NET
- Check the MS Docs article: .NET Framework Data Providers
- Check the MS Docs article: SqlConnection Class
- Check the MS Docs article: SqlCommand Class
- Check the MS Docs article: SqlParameter Class
About the author
This author pledges the content of this article is based on professional experience and not AI generated.
View all my tips
Article Last Updated: 2018-12-13