Basic Stored Procedures
These scenarios demonstrate how to call basic stored procedures that return a single resultset. This can be interpreted as a value, a row, or a collection of rows.
Future scenarios will cover topics such as:
- Multiple Resultsets
- Output parameter(s)
- Return parameter
Scenario Prototype
public interface IBasicStoredProcScenario<TEmployeeClassification, TEmployeeClassificationWithCount>
where TEmployeeClassification : class, IEmployeeClassification, new()
where TEmployeeClassificationWithCount : class, IEmployeeClassificationWithCount
{
/// <summary>
/// Execute a stored procedure that returns a collection.
/// </summary>
IList<TEmployeeClassificationWithCount> CountEmployeesByClassification();
/// <summary>
/// Execute a stored procedure that takes a parameter object and returns a scalar value.
/// </summary>
/// <param name="employeeClassification">The employee classification.</param>
/// <returns>The employee classification key.</returns>
int CreateEmployeeClassification(TEmployeeClassification employeeClassification);
/// <summary>
/// Execute a stored procedure that returns a collection, omitting the optional parameter.
/// </summary>
IList<TEmployeeClassification> GetEmployeeClassifications();
/// <summary>
/// Execute a stored procedure that returns a collection, providing an optional parameter.
/// </summary>
/// <param name="employeesClassificationKey">The employee classification key.</param>
TEmployeeClassification? GetEmployeeClassifications(int employeeClassificationKey);
}
Database Stored Procedures
CREATE PROCEDURE HR.CountEmployeesByClassification AS BEGIN SET NOCOUNT ON; SELECT COUNT(e.EmployeeKey) AS EmployeeCount, ec.EmployeeClassificationKey, ec.EmployeeClassificationName FROM HR.EmployeeClassification ec LEFT JOIN HR.Employee e ON e.EmployeeClassificationKey = ec.EmployeeClassificationKey GROUP BY ec.EmployeeClassificationKey, ec.EmployeeClassificationName ORDER BY ec.EmployeeClassificationName; RETURN 0; END;
CREATE PROCEDURE HR.CreateEmployeeClassification @EmployeeClassificationName VARCHAR(30), @IsExempt BIT, @IsEmployee BIT AS BEGIN SET NOCOUNT ON; INSERT INTO HR.EmployeeClassification ( EmployeeClassificationName, IsExempt, IsEmployee ) VALUES (@EmployeeClassificationName, @IsExempt, @IsEmployee); --Use a temp variable to ensure the correct data type DECLARE @EmployeeClassificationKey INT = SCOPE_IDENTITY(); SELECT @EmployeeClassificationKey AS EmployeeClassificationKey; RETURN 0; END;
CREATE PROCEDURE HR.GetEmployeeClassifications @EmployeeClassificationKey INT = NULL AS BEGIN SET NOCOUNT ON; SELECT ec.EmployeeClassificationKey, ec.EmployeeClassificationName, ec.IsExempt, ec.IsEmployee FROM HR.EmployeeClassification ec WHERE (@EmployeeClassificationKey IS NULL) OR (ec.EmployeeClassificationKey = @EmployeeClassificationKey); RETURN 0; END;
ADO.NET
public class BasicStoredProcScenario : SqlServerScenarioBase,
IBasicStoredProcScenario<EmployeeClassification, EmployeeClassificationWithCount>
{
public BasicStoredProcScenario(string connectionString) : base(connectionString)
{ }
public IList<EmployeeClassificationWithCount> CountEmployeesByClassification()
{
using (var con = OpenConnection())
using (var cmd = new SqlCommand("HR.CountEmployeesByClassification", con)
{ CommandType = CommandType.StoredProcedure })
using (var reader = cmd.ExecuteReader())
{
var results = new List<EmployeeClassificationWithCount>();
while (reader.Read())
results.Add(new EmployeeClassificationWithCount(reader));
return results;
}
}
public int CreateEmployeeClassification(EmployeeClassification employeeClassification)
{
if (employeeClassification == null)
throw new ArgumentNullException(nameof(employeeClassification),
$"{nameof(employeeClassification)} is null.");
using (var con = OpenConnection())
using (var cmd = new SqlCommand("HR.CreateEmployeeClassification", con)
{ CommandType = CommandType.StoredProcedure })
{
cmd.Parameters.AddWithValue("@EmployeeClassificationName",
employeeClassification.EmployeeClassificationName);
cmd.Parameters.AddWithValue("@IsEmployee", employeeClassification.IsEmployee);
cmd.Parameters.AddWithValue("@IsExempt", employeeClassification.IsExempt);
return (int)cmd.ExecuteScalar();
}
}
public IList<EmployeeClassification> GetEmployeeClassifications()
{
using (var con = OpenConnection())
using (var cmd = new SqlCommand("HR.GetEmployeeClassifications", con)
{ CommandType = CommandType.StoredProcedure })
using (var reader = cmd.ExecuteReader())
{
var results = new List<EmployeeClassification>();
while (reader.Read())
results.Add(new EmployeeClassification(reader));
return results;
}
}
public EmployeeClassification? GetEmployeeClassifications(int employeeClassificationKey)
{
using (var con = OpenConnection())
using (var cmd = new SqlCommand("HR.GetEmployeeClassifications", con)
{ CommandType = CommandType.StoredProcedure })
{
cmd.Parameters.AddWithValue("@EmployeeClassificationKey", employeeClassificationKey);
using (var reader = cmd.ExecuteReader())
{
var results = new List<EmployeeClassification>();
if (reader.Read())
return new EmployeeClassification(reader);
else
return null;
}
}
}
}
Chain
public class BasicStoredProcScenario :
IBasicStoredProcScenario<EmployeeClassification, EmployeeClassificationWithCount>
{
readonly SqlServerDataSource m_DataSource;
public BasicStoredProcScenario(SqlServerDataSource dataSource)
{
m_DataSource = dataSource;
}
public IList<EmployeeClassificationWithCount> CountEmployeesByClassification()
{
return m_DataSource.Procedure("HR.CountEmployeesByClassification")
.ToCollection<EmployeeClassificationWithCount>().Execute();
}
public int CreateEmployeeClassification(EmployeeClassification employeeClassification)
{
if (employeeClassification == null)
throw new ArgumentNullException(nameof(employeeClassification),
$"{nameof(employeeClassification)} is null.");
//Chain will ask the database which parameters it needs from the object. Other properties on the object will
//be ignored, so there's no need to manually map it to an anonymous type.
return m_DataSource.Procedure("HR.CreateEmployeeClassification", employeeClassification).ToInt32().Execute();
}
public IList<EmployeeClassification> GetEmployeeClassifications()
{
return m_DataSource.Procedure("HR.GetEmployeeClassifications")
.ToCollection<EmployeeClassification>().Execute();
}
public EmployeeClassification? GetEmployeeClassifications(int employeeClassificationKey)
{
return m_DataSource.Procedure("HR.GetEmployeeClassifications", new { employeeClassificationKey })
.ToObjectOrNull<EmployeeClassification>().Execute();
}
}
Dapper
public class BasicStoredProcScenario : ScenarioBase,
IBasicStoredProcScenario<EmployeeClassification, EmployeeClassificationWithCount>
{
public BasicStoredProcScenario(string connectionString) : base(connectionString)
{ }
public IList<EmployeeClassificationWithCount> CountEmployeesByClassification()
{
using (var con = OpenConnection())
return con.Query<EmployeeClassificationWithCount>("HR.CountEmployeesByClassification",
commandType: CommandType.StoredProcedure).ToList();
}
public int CreateEmployeeClassification(EmployeeClassification employeeClassification)
{
if (employeeClassification == null)
throw new ArgumentNullException(nameof(employeeClassification),
$"{nameof(employeeClassification)} is null.");
//Need to copy the parameters so we can exclude the unused EmployeeClassificationKey
using (var con = OpenConnection())
return con.ExecuteScalar<int>("HR.CreateEmployeeClassification",
new
{
employeeClassification.EmployeeClassificationName,
employeeClassification.IsEmployee,
employeeClassification.IsExempt
}, commandType: CommandType.StoredProcedure);
}
public IList<EmployeeClassification> GetEmployeeClassifications()
{
using (var con = OpenConnection())
return con.Query<EmployeeClassification>("HR.GetEmployeeClassifications",
commandType: CommandType.StoredProcedure).ToList();
}
public EmployeeClassification? GetEmployeeClassifications(int employeeClassificationKey)
{
using (var con = OpenConnection())
return con.Query<EmployeeClassification>("HR.GetEmployeeClassifications",
new { employeeClassificationKey }, commandType: CommandType.StoredProcedure).SingleOrDefault();
}
}
DbConnector
public class BasicStoredProcScenario : ScenarioBase,
IBasicStoredProcScenario<EmployeeClassification, EmployeeClassificationWithCount>
{
public BasicStoredProcScenario(string connectionString) : base(connectionString)
{ }
public IList<EmployeeClassificationWithCount> CountEmployeesByClassification()
{
return DbConnector.ReadToList<EmployeeClassificationWithCount>("HR.CountEmployeesByClassification",
commandType: CommandType.StoredProcedure).Execute();
}
public int CreateEmployeeClassification(EmployeeClassification employeeClassification)
{
if (employeeClassification == null)
throw new ArgumentNullException(nameof(employeeClassification),
$"{nameof(employeeClassification)} is null.");
//Need to project the parameters so we can exclude the unused EmployeeClassificationKey
return DbConnector.Scalar<int>("HR.CreateEmployeeClassification",
new
{
employeeClassification.EmployeeClassificationName,
employeeClassification.IsEmployee,
employeeClassification.IsExempt
}, commandType: CommandType.StoredProcedure).Execute();
}
public IList<EmployeeClassification> GetEmployeeClassifications()
{
return DbConnector.ReadToList<EmployeeClassification>("HR.GetEmployeeClassifications",
commandType: CommandType.StoredProcedure).Execute();
}
public EmployeeClassification? GetEmployeeClassifications(int employeeClassificationKey)
{
return DbConnector.ReadSingleOrDefault<EmployeeClassification>("HR.GetEmployeeClassifications",
new { employeeClassificationKey }, commandType: CommandType.StoredProcedure).Execute();
}
}
Entity Framework 6
TODO
Entity Framework Core
To use stored procedures that return values, a class is needed to receive the results. This is true even if a scalar value is returned.
public class EmployeeClassificationWithCount : IEmployeeClassificationWithCount
{
public int EmployeeClassificationKey { get; set; }
public string? EmployeeClassificationName { get; set; }
public int EmployeeCount { get; set; }
}
public class EmployeeClassificationKeyHolder
{
public int EmployeeClassificationKey { get; set; }
}
The receiver class should be registered in the DbContext as shown below:
partial class OrmCookbookContext : DbContext
{
//Using "= null!;" to remove the compiler warning.
//Assume that the DbContext constructor will populate these properties
public virtual DbSet<EmployeeClassificationKeyHolder> EmployeeClassificationKeyHolder { get; set; } = null!;
public virtual DbSet<EmployeeClassificationWithCount> EmployeeClassificationWithCount { get; set; } = null!;
static void RegisterEntitiesForStoredProcedures(ModelBuilder modelBuilder)
{
//Output types for stored procedures. Not generated by Scaffold-DbContext
modelBuilder.Entity<EmployeeClassificationWithCount>(entity =>
{
entity.HasNoKey();
entity.Property(e => e.EmployeeClassificationName).IsUnicode(false);
});
modelBuilder.Entity<EmployeeClassificationKeyHolder>(entity =>
{
entity.HasNoKey();
});
}
}
public class BasicStoredProcScenario :
IBasicStoredProcScenario<EmployeeClassification, EmployeeClassificationWithCount>
{
private Func<OrmCookbookContext> CreateDbContext;
public BasicStoredProcScenario(Func<OrmCookbookContext> dBContextFactory)
{
CreateDbContext = dBContextFactory;
}
public IList<EmployeeClassificationWithCount> CountEmployeesByClassification()
{
using (var context = CreateDbContext())
return context.EmployeeClassificationWithCount
.FromSqlRaw("EXEC HR.CountEmployeesByClassification;").ToList();
}
public int CreateEmployeeClassification(EmployeeClassification employeeClassification)
{
if (employeeClassification == null)
throw new ArgumentNullException(nameof(employeeClassification),
$"{nameof(employeeClassification)} is null.");
//Notes:
//EF Core cannot return scalar values from stored procedures. A holder class is needed to receive the
//results.
//Named parameters are not supported, so parameter order is important.
using (var context = CreateDbContext())
{
var temp = context.EmployeeClassificationKeyHolder
.FromSqlRaw("EXEC HR.CreateEmployeeClassification {0}, {1}, {2};",
employeeClassification.EmployeeClassificationName,
employeeClassification.IsExempt,
employeeClassification.IsEmployee
).ToList();
//Single isn't allowed for stored procedures. Thus ToList must be called first.
return temp.Single().EmployeeClassificationKey;
}
}
public IList<EmployeeClassification> GetEmployeeClassifications()
{
using (var context = CreateDbContext())
return context.EmployeeClassifications.FromSqlRaw("EXEC HR.GetEmployeeClassifications;").ToList();
}
public EmployeeClassification? GetEmployeeClassifications(int employeeClassificationKey)
{
using (var context = CreateDbContext())
{
var temp = context.EmployeeClassifications.FromSqlRaw("EXEC HR.GetEmployeeClassifications {0};",
employeeClassificationKey).ToList();
//Note that SingleOrDefault isn't allowed for stored procedures. Thus ToList must be called first.
return temp.SingleOrDefault();
}
}
}
LINQ to DB
To use stored procedures that return values, a class is needed to receive the results. This is true even if a scalar value is returned.
public class BasicStoredProcScenario : IBasicStoredProcScenario<EmployeeClassification, EmployeeClassificationWithCount>
{
public IList<EmployeeClassificationWithCount> CountEmployeesByClassification()
{
using (var db = new OrmCookbook())
return db.QueryProc<EmployeeClassificationWithCount>("HR.CountEmployeesByClassification").ToList();
}
public int CreateEmployeeClassification(EmployeeClassification employeeClassification)
{
if (employeeClassification == null)
throw new ArgumentNullException(nameof(employeeClassification), $"{nameof(employeeClassification)} is null.");
//Notes:
//LINQ to DB cannot return scalar values from stored procedures. A holder class is needed to receive the results.
using (var db = new OrmCookbook())
return db.QueryProc<EmployeeClassificationKeyHolder>("HR.CreateEmployeeClassification",
new DataParameter("@EmployeeClassificationName", employeeClassification.EmployeeClassificationName),
new DataParameter("@IsExempt", employeeClassification.IsExempt),
new DataParameter("@IsEmployee", employeeClassification.IsEmployee)
).Single().EmployeeClassificationKey;
}
public IList<EmployeeClassification> GetEmployeeClassifications()
{
using (var db = new OrmCookbook())
return db.QueryProc<EmployeeClassification>("HR.GetEmployeeClassifications").ToList();
}
public EmployeeClassification? GetEmployeeClassifications(int employeeClassificationKey)
{
using (var db = new OrmCookbook())
{
return db.QueryProc<EmployeeClassification>("HR.GetEmployeeClassifications",
new DataParameter("@EmployeeClassificationKey", employeeClassificationKey)
).SingleOrDefault();
}
}
}
LLBLGen Pro
To use stored procedures that return a resultset, a class is needed to receive the results.
public class BasicStoredProcScenario : IBasicStoredProcScenario<GetEmployeeClassificationsResultRow, CountEmployeesByClassificationResultRow>
{
public IList<CountEmployeesByClassificationResultRow> CountEmployeesByClassification()
{
// Use the generated calls to the procedure and pass in a generated projection to a poco to project the resultset
// to a set of poco instances. The Fetch... method creates a new adapter instance. We could also pass one in.
return RetrievalProcedures.FetchCountEmployeesByClassificationResultTypedView(
new QueryFactory().GetCountEmployeesByClassificationResultTypedViewProjection());
}
public int CreateEmployeeClassification(GetEmployeeClassificationsResultRow employeeClassification)
{
if(employeeClassification == null)
throw new ArgumentNullException(nameof(employeeClassification), $"{nameof(employeeClassification)} is null.");
// The stored procedure in question returns the created PK value as a resultset and not as an output parameter.
// We therefore have to call the procedure as if it's returning a resultset.
var result = RetrievalProcedures.FetchCreateEmployeeClassificationResultTypedView(
new QueryFactory().GetCreateEmployeeClassificationResultTypedViewProjection(),
employeeClassification.EmployeeClassificationName, employeeClassification.IsExempt,
employeeClassification.IsEmployee)
.FirstOrDefault();
return result?.EmployeeClassificationKey ?? 0;
}
public IList<GetEmployeeClassificationsResultRow> GetEmployeeClassifications()
{
return RetrievalProcedures.FetchGetEmployeeClassificationsResultTypedView(
new QueryFactory().GetGetEmployeeClassificationsResultTypedViewProjection(),
null);
}
public GetEmployeeClassificationsResultRow? GetEmployeeClassifications(int employeeClassificationKey)
{
return RetrievalProcedures.FetchGetEmployeeClassificationsResultTypedView(
new QueryFactory().GetGetEmployeeClassificationsResultTypedViewProjection(),
employeeClassificationKey).FirstOrDefault();
}
}
NHibernate
TODO
RepoDb
public class BasicStoredProcScenario : DbRepository<SqlConnection>,
IBasicStoredProcScenario<EmployeeClassification, EmployeeClassificationWithCount>
{
public BasicStoredProcScenario(string connectionString)
: base(connectionString, RDB.Enumerations.ConnectionPersistency.Instance)
{ }
public IList<EmployeeClassificationWithCount> CountEmployeesByClassification()
{
return ExecuteQuery<EmployeeClassificationWithCount>("[HR].[CountEmployeesByClassification]",
commandType: CommandType.StoredProcedure).AsList();
}
public int CreateEmployeeClassification(EmployeeClassification employeeClassification)
{
if (employeeClassification == null)
throw new ArgumentNullException(nameof(employeeClassification),
$"{nameof(employeeClassification)} is null.");
return ExecuteScalar<int>("[HR].[CreateEmployeeClassification]", new
{
employeeClassification.EmployeeClassificationName,
employeeClassification.IsExempt,
employeeClassification.IsEmployee
}, commandType: CommandType.StoredProcedure);
}
public IList<EmployeeClassification> GetEmployeeClassifications()
{
return ExecuteQuery<EmployeeClassification>("[HR].[GetEmployeeClassifications]",
commandType: CommandType.StoredProcedure).AsList();
}
public EmployeeClassification? GetEmployeeClassifications(int employeeClassificationKey)
{
return ExecuteQuery<EmployeeClassification>("[HR].[GetEmployeeClassifications]",
new { EmployeeClassificationKey = employeeClassificationKey },
commandType: CommandType.StoredProcedure).FirstOrDefault();
}
}
ServiceStack
public class BasicStoredProcScenario : IBasicStoredProcScenario<EmployeeClassification, EmployeeClassificationWithCount>
{
private readonly IDbConnectionFactory _dbConnectionFactory;
public BasicStoredProcScenario(IDbConnectionFactory dbConnectionFactory)
{
_dbConnectionFactory = dbConnectionFactory;
}
public IList<EmployeeClassificationWithCount> CountEmployeesByClassification()
{
using (var db = _dbConnectionFactory.OpenDbConnection())
{
using (var cmd = db.SqlProc("HR.CountEmployeesByClassification"))
{
return cmd.ConvertToList<EmployeeClassificationWithCount>();
}
}
}
public int CreateEmployeeClassification(EmployeeClassification employeeClassification)
{
if (employeeClassification == null)
throw new ArgumentNullException(nameof(employeeClassification), $"{nameof(employeeClassification)} is null.");
using (var db = _dbConnectionFactory.OpenDbConnection())
{
using (var cmd = db.SqlProc("HR.CreateEmployeeClassification", new { employeeClassification.EmployeeClassificationName, employeeClassification.IsEmployee, employeeClassification.IsExempt }))
{
return (int) cmd.Scalar();
}
}
}
public IList<EmployeeClassification> GetEmployeeClassifications()
{
using (var db = _dbConnectionFactory.OpenDbConnection())
{
using (var cmd = db.SqlProc("HR.GetEmployeeClassifications"))
{
return cmd.ConvertToList<EmployeeClassification>();
}
}
}
public EmployeeClassification? GetEmployeeClassifications(int employeeClassificationKey)
{
using (var db = _dbConnectionFactory.OpenDbConnection())
{
using (var cmd = db.SqlProc("HR.GetEmployeeClassifications", new { EmployeeClassificationKey = employeeClassificationKey }))
{
return cmd.ConvertTo<EmployeeClassification>();
}
}
}
}