CRUD Operations on Immutable Objects
These scenarios demonstrate how to perform Create, Read, Update, and Delete operations on immutable objects.
Scenario Prototype
public interface IImmutableScenario<TReadOnlyModel>
where TReadOnlyModel : class, IReadOnlyEmployeeClassification
{
/// <summary>
/// Create a new EmployeeClassification row, returning the new primary key.
/// </summary>
int Create(TReadOnlyModel classification);
/// <summary>
/// Delete a EmployeeClassification row using an object.
/// </summary>
/// <remarks>Behavior when row doesn't exist is not defined.</remarks>
void Delete(TReadOnlyModel classification);
/// <summary>
/// Gets an EmployeeClassification row by its name. Assume the name is unique.
/// </summary>
/// <remarks>Must return a null if when row doesn't exist.</remarks>
TReadOnlyModel? FindByName(string employeeClassificationName);
/// <summary>
/// Gets all EmployeeClassification rows.
/// </summary>
IReadOnlyList<TReadOnlyModel> GetAll();
/// <summary>
/// Gets an EmployeeClassification row by its primary key.
/// </summary>
/// <remarks>Behavior when row doesn't exist is not defined.</remarks>
TReadOnlyModel? GetByKey(int employeeClassificationKey);
/// <summary>
/// Update a EmployeeClassification row.
/// </summary>
/// <remarks>Behavior when row doesn't exist is not defined.</remarks>
void Update(TReadOnlyModel classification);
}
public interface IReadOnlyEmployeeClassification
{
int EmployeeClassificationKey { get; }
string? EmployeeClassificationName { get; }
bool IsEmployee { get; }
bool IsExempt { get; }
}
ADO.NET
Since ADO doesn't directly interact with models, no changes are needed for immutable objects other than to call a constructor instead of setting individual properties.
public class ImmutableScenario : SqlServerScenarioBase, IImmutableScenario<ReadOnlyEmployeeClassification>
{
public ImmutableScenario(string connectionString) : base(connectionString)
{ }
public int Create(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
const string sql = @"INSERT INTO HR.EmployeeClassification (EmployeeClassificationName, IsExempt, IsEmployee)
OUTPUT Inserted.EmployeeClassificationKey
VALUES(@EmployeeClassificationName, @IsExempt, @IsEmployee )";
using (var con = OpenConnection())
using (var cmd = new SqlCommand(sql, con))
{
cmd.Parameters.AddWithValue("@EmployeeClassificationName", classification.EmployeeClassificationName);
cmd.Parameters.AddWithValue("@IsExempt", classification.IsExempt);
cmd.Parameters.AddWithValue("@IsEmployee", classification.IsEmployee);
return (int)cmd.ExecuteScalar();
}
}
public void Delete(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
const string sql = @"DELETE HR.EmployeeClassification WHERE EmployeeClassificationKey = @EmployeeClassificationKey;";
using (var con = OpenConnection())
using (var cmd = new SqlCommand(sql, con))
{
cmd.Parameters.AddWithValue("@EmployeeClassificationKey", classification.EmployeeClassificationKey);
cmd.ExecuteNonQuery();
}
}
public void DeleteByKey(int employeeClassificationKey)
{
const string sql = @"DELETE HR.EmployeeClassification WHERE EmployeeClassificationKey = @EmployeeClassificationKey;";
using (var con = OpenConnection())
using (var cmd = new SqlCommand(sql, con))
{
cmd.Parameters.AddWithValue("@EmployeeClassificationKey", employeeClassificationKey);
cmd.ExecuteNonQuery();
}
}
public ReadOnlyEmployeeClassification? FindByName(string employeeClassificationName)
{
const string sql = @"SELECT ec.EmployeeClassificationKey, ec.EmployeeClassificationName, ec.IsExempt, ec.IsEmployee
FROM HR.EmployeeClassification ec
WHERE ec.EmployeeClassificationName = @EmployeeClassificationName;";
using (var con = OpenConnection())
using (var cmd = new SqlCommand(sql, con))
{
cmd.Parameters.AddWithValue("@EmployeeClassificationName", employeeClassificationName);
using (var reader = cmd.ExecuteReader())
{
if (!reader.Read())
return null;
return new ReadOnlyEmployeeClassification(
reader.GetInt32(reader.GetOrdinal("EmployeeClassificationKey")),
reader.GetString(reader.GetOrdinal("EmployeeClassificationName")),
reader.GetBoolean(reader.GetOrdinal("IsExempt")),
reader.GetBoolean(reader.GetOrdinal("IsEmployee"))
);
}
}
}
public IReadOnlyList<ReadOnlyEmployeeClassification> GetAll()
{
const string sql = @"SELECT ec.EmployeeClassificationKey, ec.EmployeeClassificationName, ec.IsExempt, ec.IsEmployee FROM HR.EmployeeClassification ec;";
var result = new List<ReadOnlyEmployeeClassification>();
using (var con = OpenConnection())
using (var cmd = new SqlCommand(sql, con))
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
result.Add(new ReadOnlyEmployeeClassification(
reader.GetInt32(reader.GetOrdinal("EmployeeClassificationKey")),
reader.GetString(reader.GetOrdinal("EmployeeClassificationName")),
reader.GetBoolean(reader.GetOrdinal("IsExempt")),
reader.GetBoolean(reader.GetOrdinal("IsEmployee"))
));
}
return result.ToImmutableArray();
}
}
public ReadOnlyEmployeeClassification? GetByKey(int employeeClassificationKey)
{
const string sql = @"SELECT ec.EmployeeClassificationKey, ec.EmployeeClassificationName, ec.IsExempt, ec.IsEmployee
FROM HR.EmployeeClassification ec
WHERE ec.EmployeeClassificationKey = @EmployeeClassificationKey;";
using (var con = OpenConnection())
using (var cmd = new SqlCommand(sql, con))
{
cmd.Parameters.AddWithValue("@EmployeeClassificationKey", employeeClassificationKey);
using (var reader = cmd.ExecuteReader())
{
if (!reader.Read())
return null;
return new ReadOnlyEmployeeClassification(
reader.GetInt32(reader.GetOrdinal("EmployeeClassificationKey")),
reader.GetString(reader.GetOrdinal("EmployeeClassificationName")),
reader.GetBoolean(reader.GetOrdinal("IsExempt")),
reader.GetBoolean(reader.GetOrdinal("IsEmployee"))
);
}
}
}
public void Update(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
const string sql = @"UPDATE HR.EmployeeClassification
SET EmployeeClassificationName = @EmployeeClassificationName, IsExempt = @IsExempt, IsEmployee = @IsEmployee
WHERE EmployeeClassificationKey = @EmployeeClassificationKey;";
using (var con = OpenConnection())
using (var cmd = new SqlCommand(sql, con))
{
cmd.Parameters.AddWithValue("@EmployeeClassificationKey", classification.EmployeeClassificationKey);
cmd.Parameters.AddWithValue("@EmployeeClassificationName", classification.EmployeeClassificationName);
cmd.Parameters.AddWithValue("@IsExempt", classification.IsExempt);
cmd.Parameters.AddWithValue("@IsEmployee", classification.IsEmployee);
cmd.ExecuteNonQuery();
}
}
}
Chain
Chain natively supports working with immutable objects, no conversions are needed.
To populate immutable objects, use either the InferConstructor
option or a .WithConstructor<...>
link to indicate that a non-default constructor should be used.
public class ImmutableScenario : IImmutableScenario<ReadOnlyEmployeeClassification>
{
readonly SqlServerDataSource m_DataSource;
public ImmutableScenario(SqlServerDataSource dataSource)
{
m_DataSource = dataSource;
}
public int Create(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
return m_DataSource.Insert(classification).ToInt32().Execute();
}
public void Delete(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
m_DataSource.Delete(classification).Execute();
}
public ReadOnlyEmployeeClassification? FindByName(string employeeClassificationName)
{
return m_DataSource.From<ReadOnlyEmployeeClassification>(new { employeeClassificationName })
.ToObjectOrNull(RowOptions.InferConstructor).Execute();
}
public IReadOnlyList<ReadOnlyEmployeeClassification> GetAll()
{
return m_DataSource.From<ReadOnlyEmployeeClassification>()
.ToImmutableArray(CollectionOptions.InferConstructor).Execute();
}
public ReadOnlyEmployeeClassification? GetByKey(int employeeClassificationKey)
{
return m_DataSource.GetByKey<ReadOnlyEmployeeClassification>(employeeClassificationKey)
.ToObjectOrNull<ReadOnlyEmployeeClassification>(RowOptions.InferConstructor).Execute();
}
public void Update(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
m_DataSource.Update(classification).Execute();
}
}
Dapper
Dapper natively supports working with immutable objects, no conversions are needed.
No special handling is needed to call a non-default constructor.
public class ImmutableScenario : ScenarioBase, IImmutableScenario<ReadOnlyEmployeeClassification>
{
public ImmutableScenario(string connectionString) : base(connectionString)
{
}
public int Create(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
var sql = @"INSERT INTO HR.EmployeeClassification (EmployeeClassificationName, IsExempt, IsEmployee)
OUTPUT Inserted.EmployeeClassificationKey
VALUES(@EmployeeClassificationName, @IsExempt, @IsEmployee )";
using (var con = OpenConnection())
return con.ExecuteScalar<int>(sql, classification);
}
public void Delete(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
var sql = @"DELETE HR.EmployeeClassification WHERE EmployeeClassificationKey = @EmployeeClassificationKey;";
using (var con = OpenConnection())
con.Execute(sql, classification);
}
public void DeleteByKey(int employeeClassificationKey)
{
var sql = @"DELETE HR.EmployeeClassification WHERE EmployeeClassificationKey = @EmployeeClassificationKey;";
using (var con = OpenConnection())
con.Execute(sql, new { employeeClassificationKey });
}
public ReadOnlyEmployeeClassification? FindByName(string employeeClassificationName)
{
var sql = @"SELECT ec.EmployeeClassificationKey, ec.EmployeeClassificationName, ec.IsExempt, ec.IsEmployee
FROM HR.EmployeeClassification ec
WHERE ec.EmployeeClassificationName = @EmployeeClassificationName;";
using (var con = OpenConnection())
return con.QuerySingleOrDefault<ReadOnlyEmployeeClassification>(sql, new { employeeClassificationName });
}
public IReadOnlyList<ReadOnlyEmployeeClassification> GetAll()
{
var sql = @"SELECT ec.EmployeeClassificationKey, ec.EmployeeClassificationName, ec.IsExempt, ec.IsEmployee FROM HR.EmployeeClassification ec;";
using (var con = OpenConnection())
return con.Query<ReadOnlyEmployeeClassification>(sql).ToImmutableList();
}
public ReadOnlyEmployeeClassification? GetByKey(int employeeClassificationKey)
{
var sql = @"SELECT ec.EmployeeClassificationKey, ec.EmployeeClassificationName, ec.IsExempt, ec.IsEmployee
FROM HR.EmployeeClassification ec
WHERE ec.EmployeeClassificationKey = @EmployeeClassificationKey;";
using (var con = OpenConnection())
return con.QuerySingle<ReadOnlyEmployeeClassification>(sql, new { employeeClassificationKey });
}
public void Update(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
var sql = @"UPDATE HR.EmployeeClassification
SET EmployeeClassificationName = @EmployeeClassificationName, IsExempt = @IsExempt, IsEmployee = @IsEmployee
WHERE EmployeeClassificationKey = @EmployeeClassificationKey;";
using (var con = OpenConnection())
con.Execute(sql, classification);
}
}
DbConnector
DbConnector currently does not support direct constructor mapping.
Built-in functionality, including extensions, can simply be leveraged when working with immutable objects.
public class ImmutableScenario : ScenarioBase, IImmutableScenario<ReadOnlyEmployeeClassification>
{
public ImmutableScenario(string connectionString) : base(connectionString)
{
}
public int Create(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
const string sql = @"INSERT INTO HR.EmployeeClassification (EmployeeClassificationName, IsExempt, IsEmployee)
OUTPUT Inserted.EmployeeClassificationKey
VALUES(@EmployeeClassificationName, @IsExempt, @IsEmployee);";
return DbConnector.Scalar<int>(sql, classification).Execute();
}
public void Delete(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
var sql = @"DELETE HR.EmployeeClassification WHERE EmployeeClassificationKey = @EmployeeClassificationKey;";
DbConnector.NonQuery(sql, classification).Execute();
}
public void DeleteByKey(int employeeClassificationKey)
{
var sql = @"DELETE HR.EmployeeClassification WHERE EmployeeClassificationKey = @employeeClassificationKey;";
DbConnector.NonQuery(sql, new { employeeClassificationKey }).Execute();
}
public ReadOnlyEmployeeClassification? FindByName(string employeeClassificationName)
{
var sql = @"SELECT ec.EmployeeClassificationKey, ec.EmployeeClassificationName, ec.IsExempt, ec.IsEmployee
FROM HR.EmployeeClassification ec
WHERE ec.EmployeeClassificationName = @employeeClassificationName;";
//DbConnector currently does not support direct constructor mapping (v1.2.1)
return DbConnector.ReadTo<ReadOnlyEmployeeClassification>(
sql: sql,
param: new { employeeClassificationName },
onLoad: (ReadOnlyEmployeeClassification result, IDbExecutionModel em, DbDataReader odr) =>
{
//Leverage extension from "DbConnector.Core.Extensions"
dynamic row = odr.SingleOrDefault(em.Token, em.JobCommand);
if (row != null)
result = new ReadOnlyEmployeeClassification(row.EmployeeClassificationKey, row.EmployeeClassificationName, row.IsExempt, row.IsEmployee);
//Alternative: Extract values from the DbDataReader manually
//if (odr.HasRows && odr.Read())
//{
// int employeeClassificationKey = odr.GetValue(nameof(ReadOnlyEmployeeClassification.EmployeeClassificationKey)) as int? ?? 0;
// string employeeClassificationName = odr.GetValue(nameof(ReadOnlyEmployeeClassification.EmployeeClassificationName)) as string;
// bool isExempt = odr.GetValue(nameof(ReadOnlyEmployeeClassification.IsExempt)) as bool? ?? false;
// bool isEmployee = odr.GetValue(nameof(ReadOnlyEmployeeClassification.IsEmployee)) as bool? ?? false;
// result = new ReadOnlyEmployeeClassification(employeeClassificationKey, employeeClassificationName, isExempt, isEmployee);
// if (odr.Read())//SingleOrDefault behavior
// {
// throw new InvalidOperationException("The query result has more than one result.");
// }
//}
return result;
})
.Execute();
}
public IReadOnlyList<ReadOnlyEmployeeClassification> GetAll()
{
var sql = @"SELECT ec.EmployeeClassificationKey, ec.EmployeeClassificationName, ec.IsExempt, ec.IsEmployee FROM HR.EmployeeClassification ec;";
//DbConnector currently does not support direct constructor mapping (v1.2.1)
return DbConnector.ReadTo<List<ReadOnlyEmployeeClassification>>(
sql: sql,
param: null,
onLoad: (List<ReadOnlyEmployeeClassification> result, IDbExecutionModel em, DbDataReader odr) =>
{
result = new List<ReadOnlyEmployeeClassification>();
//Extract values from the DbDataReader manually
if (odr.HasRows)
{
while (odr.Read())
{
if (em.Token.IsCancellationRequested)
return result;
int employeeClassificationKey = odr.GetValue(nameof(ReadOnlyEmployeeClassification.EmployeeClassificationKey)) as int? ?? 0;
string employeeClassificationName = odr.GetValue(nameof(ReadOnlyEmployeeClassification.EmployeeClassificationName)) as string;
bool isExempt = odr.GetValue(nameof(ReadOnlyEmployeeClassification.IsExempt)) as bool? ?? false;
bool isEmployee = odr.GetValue(nameof(ReadOnlyEmployeeClassification.IsEmployee)) as bool? ?? false;
result.Add(new ReadOnlyEmployeeClassification(employeeClassificationKey, employeeClassificationName, isExempt, isEmployee));
}
}
return result;
})
.Execute()
.ToImmutableList();
}
public ReadOnlyEmployeeClassification? GetByKey(int employeeClassificationKey)
{
var sql = @"SELECT ec.EmployeeClassificationKey, ec.EmployeeClassificationName, ec.IsExempt, ec.IsEmployee
FROM HR.EmployeeClassification ec
WHERE ec.EmployeeClassificationKey = @employeeClassificationKey;";
//DbConnector currently does not support direct constructor mapping (v1.2.1)
return DbConnector.ReadTo<ReadOnlyEmployeeClassification>(
sql: sql,
param: new { employeeClassificationKey },
onLoad: (ReadOnlyEmployeeClassification result, IDbExecutionModel em, DbDataReader odr) =>
{
//Leverage extension from "DbConnector.Core.Extensions"
dynamic row = odr.Single(em.Token, em.JobCommand);
if (row != null)
result = new ReadOnlyEmployeeClassification(row.EmployeeClassificationKey, row.EmployeeClassificationName, row.IsExempt, row.IsEmployee);
//Alternative: Extract values from the DbDataReader manually
//if (odr.HasRows && odr.Read())
//{
// int employeeClassificationKey = odr.GetValue(nameof(ReadOnlyEmployeeClassification.EmployeeClassificationKey)) as int? ?? 0;
// string employeeClassificationName = odr.GetValue(nameof(ReadOnlyEmployeeClassification.EmployeeClassificationName)) as string;
// bool isExempt = odr.GetValue(nameof(ReadOnlyEmployeeClassification.IsExempt)) as bool? ?? false;
// bool isEmployee = odr.GetValue(nameof(ReadOnlyEmployeeClassification.IsEmployee)) as bool? ?? false;
// result = new ReadOnlyEmployeeClassification(employeeClassificationKey, employeeClassificationName, isExempt, isEmployee);
// if (odr.Read())//Single behavior
// {
// throw new InvalidOperationException("The query result has more than one result.");
// }
//}
//else
//{
// //Single behavior
// throw new InvalidOperationException("The query result is empty.");
//}
return result;
})
.Execute();
}
public void Update(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
var sql = @"UPDATE HR.EmployeeClassification
SET EmployeeClassificationName = @EmployeeClassificationName, IsExempt = @IsExempt, IsEmployee = @IsEmployee
WHERE EmployeeClassificationKey = @EmployeeClassificationKey;";
DbConnector.NonQuery(sql, classification).Execute();
}
}
Entity Framework 6
Entity Framework does not directly support immutable objects. You can overcome this by using a pair of conversions between the immutable object and the mutable entity.
Objects need to be materialized client-side before being mapped to the immutable type.
public ReadOnlyEmployeeClassification(EmployeeClassification entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity), $"{nameof(entity)} is null.");
if (entity.EmployeeClassificationName == null)
throw new ArgumentNullException(nameof(entity), $"{nameof(entity.EmployeeClassificationName)} is null.");
EmployeeClassificationKey = entity.EmployeeClassificationKey;
EmployeeClassificationName = entity.EmployeeClassificationName;
IsExempt = entity.IsExempt;
IsEmployee = entity.IsEmployee;
}
public EmployeeClassification ToEntity()
{
return new EmployeeClassification()
{
EmployeeClassificationKey = EmployeeClassificationKey,
EmployeeClassificationName = EmployeeClassificationName,
IsExempt = IsExempt,
IsEmployee = IsEmployee
};
}
These conversions are used in the repository before write operations and after read operations.
public class ImmutableScenario : IImmutableScenario<ReadOnlyEmployeeClassification>
{
private Func<OrmCookbookContext> CreateDbContext;
public ImmutableScenario(Func<OrmCookbookContext> dBContextFactory)
{
CreateDbContext = dBContextFactory;
}
public int Create(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var context = CreateDbContext())
{
var temp = classification.ToEntity();
context.EmployeeClassification.Add(temp);
context.SaveChanges();
return temp.EmployeeClassificationKey;
}
}
public virtual void Delete(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var context = CreateDbContext())
{
//Find the row you wish to delete
var temp = context.EmployeeClassification.Find(classification.EmployeeClassificationKey);
if (temp != null)
{
context.EmployeeClassification.Remove(temp);
context.SaveChanges();
}
}
}
public virtual void DeleteByKey(int employeeClassificationKey)
{
using (var context = CreateDbContext())
{
//Find the row you wish to delete
var temp = context.EmployeeClassification.Find(employeeClassificationKey);
if (temp != null)
{
context.EmployeeClassification.Remove(temp);
context.SaveChanges();
}
}
}
public ReadOnlyEmployeeClassification? FindByName(string employeeClassificationName)
{
using (var context = CreateDbContext())
{
return context.EmployeeClassification
.Where(ec => ec.EmployeeClassificationName == employeeClassificationName)
.ToList() //everything below this line is client-side
.Select(x => new ReadOnlyEmployeeClassification(x)).SingleOrDefault();
}
}
public IReadOnlyList<ReadOnlyEmployeeClassification> GetAll()
{
using (var context = CreateDbContext())
{
return context.EmployeeClassification
.ToList() //everything below this line is client-side
.Select(x => new ReadOnlyEmployeeClassification(x)).ToImmutableArray();
}
}
public ReadOnlyEmployeeClassification GetByKey(int employeeClassificationKey)
{
using (var context = CreateDbContext())
{
var temp = context.EmployeeClassification.Find(employeeClassificationKey);
if (temp == null)
throw new DataException($"No row was found for key {employeeClassificationKey}.");
return new ReadOnlyEmployeeClassification(temp);
}
}
public void Update(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var context = CreateDbContext())
{
//Get a fresh copy of the row from the database
var temp = context.EmployeeClassification.Find(classification.EmployeeClassificationKey);
if (temp != null)
{
//Copy the changed fields
temp.EmployeeClassificationName = classification.EmployeeClassificationName;
temp.IsEmployee = classification.IsEmployee;
temp.IsExempt = classification.IsExempt;
context.SaveChanges();
}
}
}
}
Entity Framework Core
Entity Framework Core does not directly support immutable objects. You can overcome this by using a pair of conversions between the immutable object and the mutable entity.
public ReadOnlyEmployeeClassification(EmployeeClassification entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity), $"{nameof(entity)} is null.");
if (entity.EmployeeClassificationName == null)
throw new ArgumentNullException(nameof(entity), $"{nameof(entity.EmployeeClassificationName)} is null.");
if (entity.IsEmployee == null)
throw new ArgumentNullException(nameof(entity), $"{nameof(entity.IsEmployee)} is null.");
EmployeeClassificationKey = entity.EmployeeClassificationKey;
EmployeeClassificationName = entity.EmployeeClassificationName;
IsExempt = entity.IsExempt;
IsEmployee = entity.IsEmployee.Value;
}
public EmployeeClassification ToEntity()
{
return new EmployeeClassification()
{
EmployeeClassificationKey = EmployeeClassificationKey,
EmployeeClassificationName = EmployeeClassificationName,
IsExempt = IsExempt,
IsEmployee = IsEmployee
};
}
These conversions are used in the repository before write operations and after read operations.
public class ImmutableScenario : IImmutableScenario<ReadOnlyEmployeeClassification>
{
private Func<OrmCookbookContext> CreateDbContext;
public ImmutableScenario(Func<OrmCookbookContext> dBContextFactory)
{
CreateDbContext = dBContextFactory;
}
public int Create(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var context = CreateDbContext())
{
var temp = classification.ToEntity();
context.EmployeeClassifications.Add(temp);
context.SaveChanges();
return temp.EmployeeClassificationKey;
}
}
public virtual void Delete(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var context = CreateDbContext())
{
//Find the row you wish to delete
var temp = context.EmployeeClassifications.Find(classification.EmployeeClassificationKey);
if (temp != null)
{
context.EmployeeClassifications.Remove(temp);
context.SaveChanges();
}
}
}
public virtual void DeleteByKey(int employeeClassificationKey)
{
using (var context = CreateDbContext())
{
//Find the row you wish to delete
var temp = context.EmployeeClassifications.Find(employeeClassificationKey);
if (temp != null)
{
context.EmployeeClassifications.Remove(temp);
context.SaveChanges();
}
}
}
public ReadOnlyEmployeeClassification? FindByName(string employeeClassificationName)
{
using (var context = CreateDbContext())
{
return context.EmployeeClassifications
.Where(ec => ec.EmployeeClassificationName == employeeClassificationName)
.Select(x => new ReadOnlyEmployeeClassification(x)).SingleOrDefault();
}
}
public IReadOnlyList<ReadOnlyEmployeeClassification> GetAll()
{
using (var context = CreateDbContext())
{
return context.EmployeeClassifications.Select(x => new ReadOnlyEmployeeClassification(x)).ToImmutableArray();
}
}
public ReadOnlyEmployeeClassification GetByKey(int employeeClassificationKey)
{
using (var context = CreateDbContext())
{
var temp = context.EmployeeClassifications.Find(employeeClassificationKey);
if (temp == null)
throw new DataException($"No row was found for key {employeeClassificationKey}.");
return new ReadOnlyEmployeeClassification(temp);
}
}
public void Update(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var context = CreateDbContext())
{
//Get a fresh copy of the row from the database
var temp = context.EmployeeClassifications.Find(classification.EmployeeClassificationKey);
if (temp != null)
{
//Copy the changed fields
temp.EmployeeClassificationName = classification.EmployeeClassificationName;
temp.IsEmployee = classification.IsEmployee;
temp.IsExempt = classification.IsExempt;
context.SaveChanges();
}
}
}
}
LINQ to DB
LINQ to DB does not directly support immutable objects. You can overcome this by using a pair of conversions between the immutable object and the mutable entity.
public ReadOnlyEmployeeClassification(EmployeeClassification entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity), $"{nameof(entity)} is null.");
if (entity.EmployeeClassificationName == null)
throw new ArgumentNullException(nameof(entity), $"{nameof(entity.EmployeeClassificationName)} is null.");
EmployeeClassificationKey = entity.EmployeeClassificationKey;
EmployeeClassificationName = entity.EmployeeClassificationName;
IsExempt = entity.IsExempt;
IsEmployee = entity.IsEmployee;
}
public EmployeeClassification ToEntity()
{
return new EmployeeClassification()
{
EmployeeClassificationKey = EmployeeClassificationKey,
EmployeeClassificationName = EmployeeClassificationName,
IsExempt = IsExempt,
IsEmployee = IsEmployee
};
}
These conversions are used in the repository before write operations and after read operations.
public class ImmutableScenario : IImmutableScenario<ReadOnlyEmployeeClassification>
{
public int Create(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var db = new OrmCookbook())
{
return db.InsertWithInt32Identity(classification.ToEntity());
}
}
public virtual void Delete(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var db = new OrmCookbook())
{
db.EmployeeClassification
.Where(d => d.EmployeeClassificationKey == classification.EmployeeClassificationKey)
.Delete();
}
}
public virtual void DeleteByKey(int employeeClassificationKey)
{
using (var db = new OrmCookbook())
{
db.EmployeeClassification
.Where(d => d.EmployeeClassificationKey == employeeClassificationKey)
.Delete();
}
}
public ReadOnlyEmployeeClassification? FindByName(string employeeClassificationName)
{
using (var db = new OrmCookbook())
{
var query = from ec in db.EmployeeClassification
where ec.EmployeeClassificationName == employeeClassificationName
select ec;
return query.Select(x => new ReadOnlyEmployeeClassification(x)).SingleOrDefault();
}
}
public IReadOnlyList<ReadOnlyEmployeeClassification> GetAll()
{
using (var db = new OrmCookbook())
{
return db.EmployeeClassification
.Select(x => new ReadOnlyEmployeeClassification(x)).ToImmutableArray();
}
}
public ReadOnlyEmployeeClassification GetByKey(int employeeClassificationKey)
{
using (var db = new OrmCookbook())
{
return db.EmployeeClassification.Where(d => d.EmployeeClassificationKey == employeeClassificationKey)
.Select(x => new ReadOnlyEmployeeClassification(x)).Single();
}
}
public void Update(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var db = new OrmCookbook())
{
db.Update(classification.ToEntity());
}
}
}
LLBLGen Pro
LLBLGen Pro supports 'action' specifications on entities, e.g. an entity can only be fetched, or fetched and updated, but e.g. not deleted. An entity that's marked as 'Read' can't be updated, deleted or inserted. The scope of the recipes in this cookbook however focus on immutable data in-memory. LLBLGen Pro does not directly support these objects, You can overcome this by using a pair of conversions between the immutable object and the mutable entity.
public ReadOnlyEmployeeClassification(EmployeeClassificationEntity entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity), $"{nameof(entity)} is null.");
if (entity.EmployeeClassificationName == null)
throw new ArgumentNullException(nameof(entity), $"{nameof(entity.EmployeeClassificationName)} is null.");
EmployeeClassificationKey = entity.EmployeeClassificationKey;
EmployeeClassificationName = entity.EmployeeClassificationName;
IsExempt = entity.IsExempt;
IsEmployee = entity.IsEmployee;
}
public EmployeeClassificationEntity ToEntity()
{
return new EmployeeClassificationEntity()
{
EmployeeClassificationKey = EmployeeClassificationKey,
EmployeeClassificationName = EmployeeClassificationName,
IsExempt = IsExempt,
IsEmployee = IsEmployee
};
}
These conversions are used in the repository before write operations and after read operations.
public class ImmutableScenario : IImmutableScenario<ReadOnlyEmployeeClassification>
{
public int Create(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var adapter = new DataAccessAdapter())
{
var toPersist = classification.ToEntity();
adapter.SaveEntity(toPersist);
return toPersist.EmployeeClassificationKey;
}
}
public virtual void Delete(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
DeleteByKey(classification.EmployeeClassificationKey);
}
public virtual void DeleteByKey(int employeeClassificationKey)
{
using (var adapter = new DataAccessAdapter())
{
adapter.DeleteEntitiesDirectly(typeof(EmployeeClassificationEntity),
new RelationPredicateBucket(EmployeeClassificationFields.EmployeeClassificationKey
.Equal(employeeClassificationKey)));
}
}
public ReadOnlyEmployeeClassification? FindByName(string employeeClassificationName)
{
using (var adapter = new DataAccessAdapter())
{
return new LinqMetaData(adapter).EmployeeClassification
.Where(ec => ec.EmployeeClassificationName == employeeClassificationName)
.Select(x => new ReadOnlyEmployeeClassification(x))
.SingleOrDefault();
}
}
public IReadOnlyList<ReadOnlyEmployeeClassification> GetAll()
{
using (var adapter = new DataAccessAdapter())
{
return new LinqMetaData(adapter).EmployeeClassification.Select(x => new ReadOnlyEmployeeClassification(x)).ToImmutableArray();
}
}
public ReadOnlyEmployeeClassification GetByKey(int employeeClassificationKey)
{
using (var adapter = new DataAccessAdapter())
{
var temp = adapter.FetchNewEntity<EmployeeClassificationEntity>(
new RelationPredicateBucket(EmployeeClassificationFields.EmployeeClassificationKey
.Equal(employeeClassificationKey)));
if (temp.IsNew)
throw new DataException($"No row was found for key {employeeClassificationKey}.");
return new ReadOnlyEmployeeClassification(temp);
}
}
public void Update(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var adapter = new DataAccessAdapter())
{
//Get a fresh copy of the row from the database
var temp = adapter.FetchNewEntity<EmployeeClassificationEntity>(
new RelationPredicateBucket(EmployeeClassificationFields.EmployeeClassificationKey
.Equal(classification.EmployeeClassificationKey)));
if (!temp.IsNew)
{
//Copy the changed fields
temp.EmployeeClassificationName = classification.EmployeeClassificationName;
temp.IsEmployee = classification.IsEmployee;
temp.IsExempt = classification.IsExempt;
adapter.SaveEntity(temp);
}
}
}
}
NHibernate
NHibernate does not directly support immutable objects. You can overcome this by using a pair of conversions between the immutable object and the mutable entity.
public ReadOnlyEmployeeClassification(EmployeeClassification entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity), $"{nameof(entity)} is null.");
if (entity.EmployeeClassificationName == null)
throw new ArgumentNullException(nameof(entity), $"{nameof(entity.EmployeeClassificationName)} is null.");
EmployeeClassificationKey = entity.EmployeeClassificationKey;
EmployeeClassificationName = entity.EmployeeClassificationName;
IsExempt = entity.IsExempt;
IsEmployee = entity.IsEmployee;
}
public EmployeeClassification ToEntity()
{
return new EmployeeClassification()
{
EmployeeClassificationKey = EmployeeClassificationKey,
EmployeeClassificationName = EmployeeClassificationName,
IsExempt = IsExempt,
IsEmployee = IsEmployee
};
}
These conversions are used in the repository before write operations and after read operations.
public class ImmutableScenario : IImmutableScenario<ReadOnlyEmployeeClassification>
{
readonly ISessionFactory m_SessionFactory;
public ImmutableScenario(ISessionFactory sessionFactory)
{
m_SessionFactory = sessionFactory;
}
public int Create(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var session = m_SessionFactory.OpenSession())
{
var temp = classification.ToEntity();
session.Save(temp);
session.Flush();
return temp.EmployeeClassificationKey;
}
}
public void Delete(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var session = m_SessionFactory.OpenSession())
{
session.Delete(classification.ToEntity());
session.Flush();
}
}
public ReadOnlyEmployeeClassification? FindByName(string employeeClassificationName)
{
using (var session = m_SessionFactory.OpenStatelessSession())
{
return session.QueryOver<EmployeeClassification>().Where(e => e.EmployeeClassificationName == employeeClassificationName).List()
.Select(x => new ReadOnlyEmployeeClassification(x)).SingleOrDefault();
}
}
public IReadOnlyList<ReadOnlyEmployeeClassification> GetAll()
{
using (var session = m_SessionFactory.OpenStatelessSession())
{
return session.QueryOver<EmployeeClassification>().List()
.Select(x => new ReadOnlyEmployeeClassification(x)).ToImmutableArray();
}
}
public ReadOnlyEmployeeClassification? GetByKey(int employeeClassificationKey)
{
using (var session = m_SessionFactory.OpenStatelessSession())
{
var result = session.Get<EmployeeClassification>(employeeClassificationKey);
return new ReadOnlyEmployeeClassification(result);
}
}
public void Update(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var session = m_SessionFactory.OpenSession())
{
session.Update(classification.ToEntity());
session.Flush();
}
}
}
RepoDb
RepoDb does not directly support immutable objects. You have to manage the conversion between mutable and immutable objects in order to make it work.
Below is a sample snippet for immutable class.
[Map("[HR].[EmployeeClassification]")]
public class ReadOnlyEmployeeClassification : IReadOnlyEmployeeClassification
{
public ReadOnlyEmployeeClassification(EmployeeClassification classification)
{
if (classification?.EmployeeClassificationName == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification.EmployeeClassificationName)} is null.");
EmployeeClassificationKey = classification.EmployeeClassificationKey;
EmployeeClassificationName = classification.EmployeeClassificationName;
IsExempt = classification.IsExempt;
IsEmployee = classification.IsEmployee;
}
public ReadOnlyEmployeeClassification(int employeeClassificationKey,
string employeeClassificationName,
bool isExempt,
bool isEmployee)
{
EmployeeClassificationKey = employeeClassificationKey;
EmployeeClassificationName = employeeClassificationName;
IsExempt = isExempt;
IsEmployee = isEmployee;
}
public int EmployeeClassificationKey { get; }
public string EmployeeClassificationName { get; }
public bool IsEmployee { get; }
public bool IsExempt { get; }
}
Below is a sample snippet for mutable class.
[Map("[HR].[EmployeeClassification]")]
public class EmployeeClassification : IEmployeeClassification
{
public EmployeeClassification()
{
}
public EmployeeClassification(IReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
EmployeeClassificationKey = classification.EmployeeClassificationKey;
EmployeeClassificationName = classification.EmployeeClassificationName;
IsExempt = classification.IsExempt;
IsEmployee = classification.IsEmployee;
}
public int EmployeeClassificationKey { get; set; }
public string? EmployeeClassificationName { get; set; }
public bool IsEmployee { get; set; }
public bool IsExempt { get; set; }
internal ReadOnlyEmployeeClassification ToImmutable()
{
return new ReadOnlyEmployeeClassification(this);
}
}
Below is the immutable repository.
public class ImmutableScenario : BaseRepository<ReadOnlyEmployeeClassification, SqlConnection>,
IImmutableScenario<ReadOnlyEmployeeClassification>
{
public ImmutableScenario(string connectionString)
: base(connectionString, RDB.Enumerations.ConnectionPersistency.Instance)
{ }
public int Create(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
return Insert<int>(classification);
}
public void Delete(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
base.Delete(classification);
}
public void DeleteByKey(int employeeClassificationKey)
{
Delete(employeeClassificationKey);
}
public ReadOnlyEmployeeClassification? FindByName(string employeeClassificationName)
{
return Query(e => e.EmployeeClassificationName == employeeClassificationName)
.FirstOrDefault();
}
public IReadOnlyList<ReadOnlyEmployeeClassification> GetAll()
{
return QueryAll()
.ToImmutableList();
}
public ReadOnlyEmployeeClassification? GetByKey(int employeeClassificationKey)
{
return Query(employeeClassificationKey)
.FirstOrDefault();
}
public void Update(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
base.Update(classification);
}
}
ServiceStack
public class ImmutableScenario : IImmutableScenario<ReadOnlyEmployeeClassification>
{
private readonly IDbConnectionFactory _dbConnectionFactory;
public ImmutableScenario(IDbConnectionFactory dbConnectionFactory)
{
_dbConnectionFactory = dbConnectionFactory;
}
public int Create(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var db = _dbConnectionFactory.OpenDbConnection())
{
return (int)db.Insert(new EmployeeClassification().PopulateWith(classification), true);
}
}
public void Delete(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var db = _dbConnectionFactory.OpenDbConnection())
{
var deleted = db.Delete<EmployeeClassification>(r => r.Id == classification.Id);
if (deleted != 1)
throw new DataException($"No row was found for key {classification.EmployeeClassificationKey}.");
}
}
public void DeleteByKey(int employeeClassificationKey)
{
using (var db = _dbConnectionFactory.OpenDbConnection())
{
var deleted = db.Delete<EmployeeClassification>(r => r.Id == employeeClassificationKey);
if (deleted != 1)
throw new DataException($"No row was found for key {employeeClassificationKey}.");
}
}
public ReadOnlyEmployeeClassification? FindByName(string employeeClassificationName)
{
using (var db = _dbConnectionFactory.OpenDbConnection())
{
var temp = db.Single<EmployeeClassification>(r =>
r.EmployeeClassificationName == employeeClassificationName);
return temp == null ? null : new ReadOnlyEmployeeClassification(temp);
}
}
public IReadOnlyList<ReadOnlyEmployeeClassification> GetAll()
{
using (var db = _dbConnectionFactory.OpenDbConnection())
{
return db.Select<EmployeeClassification>()
.Select(x => new ReadOnlyEmployeeClassification(x))
.ToImmutableArray();
}
}
public ReadOnlyEmployeeClassification? GetByKey(int employeeClassificationKey)
{
using (var db = _dbConnectionFactory.OpenDbConnection())
{
var temp = db.Single<EmployeeClassification>(r =>
r.Id == employeeClassificationKey);
if (temp == null)
throw new DataException($"No row was found for key {employeeClassificationKey}.");
return new ReadOnlyEmployeeClassification(temp);
}
}
public void Update(ReadOnlyEmployeeClassification classification)
{
if (classification == null)
throw new ArgumentNullException(nameof(classification), $"{nameof(classification)} is null.");
using (var db = _dbConnectionFactory.OpenDbConnection())
{
db.Update<EmployeeClassification>(new
{
classification.IsEmployee,
classification.IsExempt,
classification.EmployeeClassificationName
}, r => r.Id == classification.Id);
}
}
}