Lets say I have 2 tables. ProductCategory
and Product
. I have 1 generic repository that can handle both tables:
public class Generi
Demonstrating a solution with only one class would be
public class Session : ISession
{
private readonly DbContext _dbContext;
public Session(DbContext dbContext)
{
_dbContext = dbContext;
}
public TEntity Single(Expression> expression) where TEntity : class
{
return _dbContext.Set().SingleOrDefault(expression);
}
public IQueryable Query() where TEntity : class
{
return _dbContext.Set().AsQueryable();
}
public void Commit()
{
try { _dbContext.SaveChanges(); }
catch (DbEntityValidationException ex)
{
var m = ex.ToFriendlyMessage();
throw new DbEntityValidationException(m);
}
}
public void Dispose()
{
_dbContext.Dispose();
}
public void Add(IEnumerable items) where TEntity : class
{
items.ToList().ForEach(Add);
}
public void Add(TEntity item) where TEntity : class
{
_dbContext.Set().Add(item);
}
public void Remove(TEntity item) where TEntity : class
{
_dbContext.Set().Remove(item);
}
public void Remove(Expression> expression) where TEntity : class
{
var items = Query().Where(expression);
Remove(items);
}
public void Remove(IEnumerable items) where TEntity : class
{
items.ToList().ForEach(Remove);
}
}
and then your usage can be
public class User
{
public int? Id { get; set; }
public string Name { get; set; }
public DateTime Dob { get; set; }
}
public class Usage
{
private readonly ISession _session;
public Usage(ISession session) { _session = session; }
public void Create(User user)
{
_session.Add(user);
_session.Commit();
}
public void Update(User user)
{
var existing = _session.Single(x => x.Id == user.Id);
// this gets cumbursome for an entity with many properties.
// I would use some thing like valueinjecter (nuget package)
// to inject the existing customer values into the one retreived from the Db.
existing.Name = user.Name;
existing.Dob = user.Dob;
_session.Commit();
}
}
I have deliberately not included a Repository class. To have a class encapsulate both queries and commands for every entity is an over kill and a needless abstraction. Its almost a design flaw at a fundamental level. Queries and commands are fundamentally different concerns. Queries in the most simplest manner can be created as extensions methods on the ISession
interface. Commands can be done using a few classes like such..
public interface ICommand
{
void ApplyTo(TSource source);
}
public interface ICommandHandler
{
void Handle(ICommand command);
}
public class LinqCommandHandler : ICommandHandler
{
private readonly ISession _session;
public LinqCommandHandler(ISession session)
{
_session = session;
}
public void Handle(ICommand command)
{
command.ApplyTo(_session);
_session.Commit();
}
}
public class UpdateDobForUserName : ICommand
{
public string UserName { get; set; }
public DateTime Dob { get; set; }
public void OnSend(IStore store)
{
var existing = store.Query().SingleOrDefault(x => x.Name == UserName);
existing.Dob = Dob;
}
}
public class Usage
{
private readonly ICommandHandler _commandHandler;
public Usage(ICommandHandler commandHandler)
{
_commandHandler = commandHandler;
}
public void Update()
{
var command = new UpdateDobForUserName {UserName = "mary", Dob = new DateTime(1960, 10, 2)};
_commandHandler.Handle(command);
}
}
The IStore
above is the same as the Session
class, except that it doesn't implement the IDisposable
interface and doesn't have a Commit()
method. The ISession
then obviously inherits from an IStore
and also implements IDisposable
and has one method Commit()
. This ensures an ICommand
can never open or dispose connections and cannot commit. Its responsibility is to define a command and define how its applied. Who applies it and what happens and what not on command application is a different responsibility which is with the ICommandHandler
.