Currently we use the UnitOfWork design pattern which we cannot change. The generic repository class looks like this:
public class Repository<T> : IDisposable, IRepository<T> where T : class
{
// Create our private properties
private readonly DbContext context;
private readonly DbSet<T> dbEntitySet;
/// <summary>
/// Default constructor
/// </summary>
/// <param name="context">The database context</param>
public Repository(DbContext context)
{
// If no context is supplied, throw an error
if (context == null)
throw new ArgumentNullException("context");
// Assign our context and entity set
this.context = context;
this.dbEntitySet = context.Set<T>();
}
/// <summary>
/// Allows the execution of stored procedures
/// </summary>
/// <typeparam name="T">The entity model</typeparam>
/// <param name="sql">The name of the stored procedure</param>
/// <param name="parameters">Option list of parameters</param>
/// <returns></returns>
public DbRawSqlQuery<T> SQLQuery(string sql, params object[] parameters)
{
return this.context.Database.SqlQuery<T>(sql, parameters);
}
/// <summary>
/// Gets all the entities
/// </summary>
/// <param name="includes">Option includes for eager loading</param>
/// <returns></returns>
public IQueryable<T> GetAll(params string[] includes)
{
IQueryable<T> query = this.dbEntitySet;
foreach (var include in includes)
query = query.Include(include);
return query;
}
/// <summary>
/// Creates an entity
/// </summary>
/// <param name="model"></param>
public void Create(T model)
{
this.dbEntitySet.Add(model);
}
/// <summary>
/// Updates an entity
/// </summary>
/// <param name="model"></param>
public void Update(T model)
{
this.context.Entry<T>(model).State = EntityState.Modified;
}
/// <summary>
/// Removes an entity
/// </summary>
/// <param name="model"></param>
public void Remove(T model)
{
this.context.Entry<T>(model).State = EntityState.Deleted;
}
/// <summary>
/// Dispose method
/// </summary>
public void Dispose()
{
this.context.Dispose();
}
and the service looks like this:
public class Service<T> where T : class
{
private readonly IUnitOfWork unitOfWork;
private readonly IRepository<T> repository;
protected IUnitOfWork UnitOfWork
{
get { return this.unitOfWork; }
}
protected IRepository<T> Repository
{
get { return this.repository; }
}
public Service(IUnitOfWork unitOfWork)
{
if (unitOfWork == null)
throw new ArgumentNullException("unitOfWork");
this.unitOfWork = unitOfWork;
this.repository = unitOfWork.GetRepository<T>();
}
}
and the UnitOfWork class looks like this:
/// <summary>
/// Used to handle the saving of database changes
/// </summary>
/// <typeparam name="TContext"></typeparam>
public class UnitOfWork<TContext> : IUnitOfWork where TContext : DbContext, new()
{
// Private properties
private readonly DbContext context;
private Dictionary<Type, object> repositories;
// Public properties
public DbContext Context { get { return this.context; } }
/// <summary>
/// Default constructor
/// </summary>
public UnitOfWork()
{
this.context = new TContext();
repositories = new Dictionary<Type, object>();
}
/// <summary>
/// Gets the entity repository
/// </summary>
/// <typeparam name="TEntity">The entity model</typeparam>
/// <returns></returns>
public IRepository<TEntity> GetRepository<TEntity>() where TEntity : class
{
// If our repositories have a matching repository, return it
if (repositories.Keys.Contains(typeof(TEntity)))
return repositories[typeof(TEntity)] as IRepository<TEntity>;
// Create a new repository for our entity
var repository = new Repository<TEntity>(context);
// Add to our list of repositories
repositories.Add(typeof(TEntity), repository);
// Return our repository
return repository;
}
/// <summary>
/// Saves the database changes asynchronously
/// </summary>
/// <returns></returns>
public async Task SaveChangesAsync()
{
try
{
// Save the changes to the database
await this.context.SaveChangesAsync();
// If there is an error
} catch (DbEntityValidationException ex) {
// Retrieve the error messages as a list of strings.
var errorMessages = ex.EntityValidationErrors.SelectMany(x => x.ValidationErrors).Select(x => x.ErrorMessage);
// Join the list to a single string.
var fullErrorMessage = string.Join("; ", errorMessages);
// Combine the original exception message with the new one.
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
// Throw a new DbEntityValidationException with the improved exception message.
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
this.context.Dispose();
}
}
Now I have a ProductService which looks like this:
/// <summary>
/// Handles all product related methods
/// </summary>
public class ProductService : Service<Product>
{
/// <summary>
/// The default constructor
/// </summary>
/// <param name="unitOfWork"></param>
public ProductService(IUnitOfWork unitOfWork)
: base(unitOfWork)
{
}
/// <summary>
/// Gets all products
/// </summary>
/// <param name="includes">Optional eager loading includes</param>
/// <returns></returns>
public async Task<List<Product>> GetAllAsync(params string[] includes)
{
// Return as an asynchronous list
return await this.Repository.GetAll(includes).ToListAsync();
}
/// <summary>
/// Gets a single product by id
/// </summary>
/// <param name="id">The id of the product</param>
/// <param name="includes">Optional eager loading includes</param>
/// <returns></returns>
public async Task<Product> GetAsync(int id, params string[] includes)
{
var models = await this.GetAllAsync(includes);
return models.Where(model => model.Id == id).SingleOrDefault();
}
/// <summary>
/// Create a product
/// </summary>
/// <param name="model">The product model</param>
public void Create(Product model)
{
// Create a team
this.Repository.Create(model);
}
/// <summary>
/// Update a product
/// </summary>
/// <param name="model">The product model</param>
public void Update(Product model)
{
// Update a team
this.Repository.Update(model);
}
/// <summary>
/// Delete a product
/// </summary>
/// <param name="model">The product model</param>
public void Remove(Product model)
{
// Remove a team
this.Repository.Remove(model);
}
}
I have set up my Code First database to use Cascading Delete when deleting a product. There are quite a few 1 to 1 table relationships set up. If I use SQL Management Studio I can manually delete a product from the database and it will delete all the related entries. But, if I try to delete it from my application I get this error:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
Ideally I would like to loop through the children of a generic class (so in the Reposity class) and check to see if cascading delete is enabled for that entity, if it is I would like to delete it. If not, do nothing.
Does anyone know if that is possible or know of another solution that does not involve dropping the UnitOfWork design pattern?