- Azure SQL bindings for Azure Functions - .NET
These instructions will guide you through creating your Function Project and adding the SQL binding extension. This only needs to be done once for every function project you create. If you have one created already you can skip this step.
-
Install Azure Functions Core Tools
-
Create a function project for .NET:
mkdir MyApp cd MyApp func init --worker-runtime dotnet
-
Enable SQL bindings on the function project. More information can be found in the Azure SQL bindings for Azure Functions docs.
Install the extension.
dotnet add package Microsoft.Azure.WebJobs.Extensions.Sql --prerelease
See Input Binding Overview for general information about the Azure SQL Input binding.
The SqlAttribute for Input bindings takes four arguments:
- CommandText: Represents either a query string or the name of a stored procedure based on the value of the CommandType.
- ConnectionStringSetting: Specifies the name of the app setting that contains the SQL connection string used to connect to a database. The connection string must follow the format specified here.
- CommandType: Specifies whether CommandText is a query (
System.Data.CommandType.Text
) or a stored procedure (System.Data.CommandType.StoredProcedure
). Defaults toCommandType.Text
. - Parameters: The parameters to the query/stored procedure. This string must follow the format "@param1=param1,@param2=param2" where @param1 is the name of the parameter and param1 is the parameter value. Each pair of parameter name, parameter value is separated by a comma. Within each pair, the parameter name and value is separated by an equals sign. This means that neither the parameter name nor value can contain "," or "=". To specify a
NULL
parameter value, do "@param1=null,@param2=param2". To specify an empty string as a value, do "@param1=,@param2=param2", i.e. do not put any text after the equals sign of the corresponding parameter name. This argument is auto-resolvable (see Query String examples).
The following are valid binding types for the result of the query/stored procedure execution:
- IEnumerable<T>: Each element is a row of the result represented by
T
, whereT
is a user-defined POCO, or Plain Old C# Object.T
should follow the structure of a row in the queried table. See the Query String section for an example of whatT
should look like. - IAsyncEnumerable<T>: Each element is again a row of the result represented by
T
, but the rows are retrieved "lazily". A row of the result is only retrieved whenMoveNextAsync
is called on the enumerator. This is useful in the case that the query can return a very large number of rows. - String: A JSON string representation of the rows of the result (an example is provided here).
- SqlCommand: The SqlCommand is populated with the appropriate query and parameters, but the associated connection is not opened. It is the responsibility of the user to execute the command and read in the results. This is useful in the case that the user wants more control over how the results are read in. An example is provided here.
The repo contains examples of each of these binding types here. A few examples are also included below.
Note: This tutorial requires that a SQL database is setup as shown in Create a SQL Server.
-
Open your project that you created in Create a Function Project in VS Code
-
Press 'F1' and search for 'Azure Functions: Create Function'
-
Choose HttpTrigger -> (Provide a function name) -> Company.namespace -> anonymous
-
In the file that opens, replace the
public static async Task<IActionResult> Run
block with the below code.public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "employees")] HttpRequest req, ILogger log, [Sql("select * from Employees", "SqlConnectionString")] IEnumerable<Employee> employee) { return new OkObjectResult(employee); }
In the above,
select * from Employees
is the SQL script run by the input binding. The CommandType on the line below specifies whether the first line is a query or a stored procedure. The next parameter specifies that the app setting that contains the SQL connection string used to connect to the database is "SqlConnectionString." For more information on this, see the SqlAttribute for Input Bindings section -
Add 'using System.Collections.Generic;' to the namespaces list at the top of the page.
-
Currently, there is an error for the IEnumerable. We'll fix this by creating an Employee class.
-
Create a new file and call it 'Employee.cs'
-
Paste the below in the file. These are the column names of our SQL table. Note that the casing of the Object field names and the table column names must match.
namespace Company.Function { public class Employee { public int EmployeeId { get; set; } public string LastName { get; set; } public string FirstName { get; set; } public string Company { get; set; } public string Team { get; set; } } }
-
Navigate back to your HttpTrigger file. We can ignore the 'Run' warning for now.
-
Open the local.settings.json file, and in the brackets for "Values," verify there is a 'SqlConnectionString.' If not, add it.
-
Hit 'F5' to run your code. This will start up the Functions Host with a local HTTP Trigger and SQL Input Binding.
-
Click the link that appears in your terminal.
-
You should see your database output in the browser window.
-
Congratulations! You have successfully created your first SQL input binding!
The database scripts used for the following samples can be found here.
The input binding executes the select * from Products where Cost = @Cost
query, returning the result as an IEnumerable<Product>
, where Product is a user-defined POCO. The Parameters argument passes the {cost}
specified in the URL that triggers the function, getproducts/{cost}
, as the value of the @Cost
parameter in the query. CommandType is set to System.Data.CommandType.Text
, since the constructor argument of the binding is a raw query.
[FunctionName("GetProducts")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "getproducts/{cost}")]
HttpRequest req,
[Sql("select * from Products where Cost = @Cost",
"SqlConnectionString",
parameters: "@Cost={cost}")]
IEnumerable<Product> products)
{
return (ActionResult)new OkObjectResult(products);
}
Product
is a user-defined POCO that follows the structure of the Products table. It represents a row of the Products table, with field names and types copying those of the Products table schema. For example, if the Products table has the following three columns
- ProductId: int
- Name: varchar
- Cost: int
Then the Product
class would look like
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public int Cost { get; set; }
}
In this case, the parameter value of the @Name
parameter is an empty string.
[FunctionName("GetProductsNameEmpty")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "getproducts-nameempty/{cost}")]
HttpRequest req,
[Sql("select * from Products where Cost = @Cost and Name = @Name",
"SqlConnectionString",
parameters: "@Cost={cost},@Name=")]
IEnumerable<Product> products)
{
return (ActionResult)new OkObjectResult(products);
}
If the {name}
specified in the getproducts-namenull/{name}
URL is "null", the query returns all rows for which the Name column is NULL
. Otherwise, it returns all rows for which the value of the Name column matches the string passed in {name}
[FunctionName("GetProductsNameNull")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "getproducts-namenull/{name}")]
HttpRequest req,
[Sql("if @Name is null select * from Products where Name is null else select * from Products where @Name = name",
"SqlConnectionString",
parameters: "@Name={name}")]
IEnumerable<Product> products)
{
return (ActionResult)new OkObjectResult(products);
}
SelectsProductCost
is the name of a procedure stored in the user's database. In this case, CommandType is System.Data.CommandType.StoredProcedure
. The parameter value of the @Cost
parameter in the procedure is once again the {cost}
specified in the getproducts-storedprocedure/{cost}
URL.
[FunctionName("GetProductsStoredProcedure")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "getproducts-storedprocedure/{cost}")]
HttpRequest req,
[Sql("SelectProductsCost",
"SqlConnectionString",
System.Data.CommandType.StoredProcedure,
"@Cost={cost}")]
IEnumerable<Product> products)
{
return (ActionResult)new OkObjectResult(products);
}
Using the IAsyncEnumerable
binding generally requires that the Run
function be async
.
Note: It is important to call DisposeAsync
at the end of function execution to make sure all resources used by the enumerator are freed. If DisposeAsync
is not called, the SQL connection will remain open and result in resource leaks.
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "getproducts-async/{cost}")]
HttpRequest req,
[Sql("select * from Products where cost = @Cost",
"SqlConnectionString",
parameters: "@Cost={cost}")]
IAsyncEnumerable<Product> products)
{
var enumerator = products.GetAsyncEnumerator();
var productList = new List<Product>();
while (await enumerator.MoveNextAsync())
{
productList.Add(enumerator.Current);
}
await enumerator.DisposeAsync();
return (ActionResult)new OkObjectResult(productList);
}
See Output Binding Overview for general information about the Azure SQL Output binding.
The SqlAttribute for Output bindings takes two arguments:
- CommandText: Represents the name of the table into which rows will be upserted.
- ConnectionStringSetting: Specifies the name of the app setting that contains the SQL connection string used to connect to a database. The connection string must follow the format specified here.
The following are valid binding types for the rows to be upserted into the table:
- ICollector<T>/IAsyncCollector<T>: Each element is a row represented by
T
, whereT
is a user-defined POCO, or Plain Old C# Object.T
should follow the structure of a row in the queried table. See the Query String for an example of whatT
should look like. - T: Used when just one row is to be upserted into the table.
- T[]: Each element is again a row of the result represented by
T
. This output binding type requires manual instantiation of the array in the function.
The repo contains examples of each of these binding types here. A few examples are also included below.
Note: This tutorial requires that a SQL database is setup as shown in Create a SQL Server, and that you have the 'Employee.cs' class from the Setup for Input Bindings section.
-
Open your app in VS Code
-
Press 'F1' and search for 'Azure Functions: Create Function'
-
Choose HttpTrigger -> (Provide a function name) -> Company.namespace is fine -> anonymous
-
In the file that opens, replace the
public static async Task<IActionResult> Run
block with the below codepublic static IActionResult Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "addemployees-array")] HttpRequest req, ILogger log, [Sql("dbo.Employees", "SqlConnectionString")] out Employee[] output) { output = new Employee[] { new Employee { EmployeeId = 1, FirstName = "Hello", LastName = "World", Company = "Microsoft", Team = "Functions" }, new Employee { EmployeeId = 2, FirstName = "Hi", LastName = "SQLupdate", Company = "Microsoft", Team = "Functions" }, }; return new CreatedResult($"/api/addemployees-array", output); }
In the above, "dbo.Employees" is the name of the table our output binding is upserting into. The line below is similar to the input binding and specifies where our SqlConnectionString is. For more information on this, see the SqlAttribute for Output Bindings section
-
Hit 'F5' to run your code. Click the link to upsert the output array values in your SQL table. Your upserted values should launch in the browser.
-
Congratulations! You have successfully created your first SQL output binding!
When using an ICollector
, it is not necessary to instantiate it. The function can add rows to the ICollector
directly, and its contents are automatically upserted once the function exits.
[FunctionName("AddProductsCollector")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "addproducts-collector")] HttpRequest req,
[Sql("Products", "SqlConnectionString")] ICollector<Product> products)
{
var newProducts = GetNewProducts(5000);
foreach (var product in newProducts)
{
products.Add(product);
}
return new CreatedResult($"/api/addproducts-collector", "done");
}
It is also possible to force an upsert within the function by calling FlushAsync()
on an IAsyncCollector
[FunctionName("AddProductsAsyncCollector")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "addproducts-asynccollector")] HttpRequest req,
[Sql("Products", "SqlConnectionString")] IAsyncCollector<Product> products)
{
var newProducts = GetNewProducts(5000);
foreach (var product in newProducts)
{
await products.AddAsync(product);
}
// Rows are upserted here
await products.FlushAsync();
newProducts = GetNewProducts(5000);
foreach (var product in newProducts)
{
await products.AddAsync(product);
}
return new CreatedResult($"/api/addproducts-collector", "done");
}
This output binding type requires explicit instantiation within the function body. Note also that the Product[]
array must be prefixed by out
when attached to the output binding
[FunctionName("AddProductsArray")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "addproducts-array")]
HttpRequest req,
[Sql("dbo.Products", "SqlConnectionString")] out Product[] output)
{
// Suppose that the ProductId column is the primary key in the Products table, and the
// table already contains a row with ProductId = 1. In that case, the row will be updated
// instead of inserted to have values Name = "Cup" and Cost = 2.
output = new Product[2];
var product = new Product();
product.ProductId = 1;
product.Name = "Cup";
product.Cost = 2;
output[0] = product;
product = new Product();
product.ProductId = 2;
product.Name = "Glasses";
product.Cost = 12;
output[1] = product;
return new CreatedResult($"/api/addproducts-array", output);
}
When binding to a single row, it is also necessary to prefix the row with out
[FunctionName("AddProduct")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "addproduct")]
HttpRequest req,
[Sql("Products", "SqlConnectionString")] out Product product)
{
product = new Product
{
Name = req.Query["name"],
ProductId = int.Parse(req.Query["id"]),
Cost = int.Parse(req.Query["cost"])
};
return new CreatedResult($"/api/addproduct", product);
}
See Trigger Binding Overview for general information about the Azure SQL Trigger binding.
The SqlAttribute for Trigger bindings takes two arguments
- TableName: Represents the name of the table to be monitored for changes.
- ConnectionStringSetting: Specifies the name of the app setting that contains the SQL connection string used to connect to a database. The connection string must follow the format specified here.
The trigger binding can bind to type IReadOnlyList<SqlChange<T>>
:
- IReadOnlyList<SqlChange<T>>: If there are multiple rows updated in the SQL table, the user function will get invoked with a batch of changes, where each element is a
SqlChange
object. HereT
is a generic type-argument that can be substituted with a user-defined POCO, or Plain Old C# Object, representing the user table row. The POCO should therefore follow the schema of the queried table. See the Query String section for an example of what the POCO should look like. The two properties of classSqlChange<T>
areItem
of typeT
which represents the table row andOperation
of typeSqlChangeOperation
which indicates the kind of row operation (insert, update, or delete) that triggered the user function.
Note that for insert and update operations, the user function receives POCO object containing the latest values of table columns. For delete operation, only the properties corresponding to the primary keys of the row are populated.
Any time when the changes happen to the "Products" table, the user function will be invoked with a batch of changes. The changes are processed sequentially, so if there are a large number of changes pending to be processed, the function will be passed a batch containing the earliest changes first.
[FunctionName("ProductsTrigger")]
public static void Run(
[SqlTrigger("Products", "SqlConnectionString")]
IReadOnlyList<SqlChange<Product>> changes,
ILogger logger)
{
foreach (SqlChange<Product> change in changes)
{
Product product = change.Item;
logger.LogInformation($"Change operation: {change.Operation}");
logger.LogInformation($"ProductId: {product.ProductId}, Name: {product.Name}, Cost: {product.Cost}");
}
}
Note: This tutorial requires that a SQL database is setup as shown in Create a SQL Server, and that you have the 'Employee.cs' file from the Setup for Input Bindings section.
-
Create a new file with the following content:
using System.Collections.Generic; using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; using Microsoft.Azure.WebJobs.Extensions.Sql; namespace Company.Function { public static class EmployeeTrigger { [FunctionName("EmployeeTrigger")] public static void Run( [SqlTrigger("[dbo].[Employees]", "SqlConnectionString")] IReadOnlyList<SqlChange<Employee>> changes, ILogger logger) { foreach (SqlChange<Employee> change in changes) { Employee employee = change.Item; logger.LogInformation($"Change operation: {change.Operation}"); logger.LogInformation($"EmployeeID: {employee.EmployeeId}, FirstName: {employee.FirstName}, LastName: {employee.LastName}, Company: {employee.Company}, Team: {employee.Team}"); } } } }
-
Skip these steps if you have not completed the output binding tutorial.
- Open your output binding file and modify some of the values. For example, change the value of Team column from 'Functions' to 'Azure SQL'.
- Hit 'F5' to run your code. Click the link of the HTTP trigger from the output binding tutorial.
-
Update, insert, or delete rows in your SQL table while the function app is running and observe the function logs.
-
You should see the new log messages in the Visual Studio Code terminal containing the values of row-columns after the update operation.
-
Congratulations! You have successfully created your first SQL trigger binding!