I am working in a shop where we tie into multiple different vendors to share data. I am also tasked with "bringing the code base up to the 4.x framework". To start, I understand that Entity Framework is a Repository/UoW pattern and really does not gain anything by wrapping it in home-spun Repository/UoW patterns. While I will eventually add EF at a later date, I don't have it in the existing code base. Instead, I have:
- ADO.NET
- OLEDB
- Web Service (SOAP)
- Web Socket (and a whole bunch of ugly)
And that's what I've picked out so far...
So, I am trying to build a basic Repository and UoW from the bottom up that will, eventually, present the same way to the developer in the end. What I have so far are 3 projects:
- Me.Data // Has all the base interfaces and abstract classes
- Me.Data.Cars // Handles the data via a SOAP based web service
- Me.Data.Symitar // Handles the data via a web socket
Starting from the bottom:
NAMESPACE : Me.Data
IRepository
public interface IRepository<T, in TId>
{
T Insert(T entity);
T Update(T entity);
T Delete(T entity);
IList<T> Get(Expression<Func<T, bool>> predicate);
IList<T> GetAll();
T GetById(TId id);
}
IRepositoryWebService
public interface IRepositoryWebService<T, in TId> : IRepository<T, TId> where T : class
{
T Get(T entity);
IList<T> GetList(T entity);
}
RepositoryWebService
public abstract class RepositoryWebService<T, TId> : IRepositoryWebService<T, TId> where T : class
{
protected IUnitOfWork UnitOfWork;
protected RepositoryWebService(IUnitOfWork unitOfWork) { UnitOfWork = unitOfWork; }
#region IRepositoryWebService<TSend, TReceive> Members
public virtual T Delete(T entity) { throw new NotImplementedException("This web service does not have an delete/del endpoint for this entity!"); }
public virtual IList<T> Get(Expression<Func<T, bool>> predicate) { throw new NotImplementedException("This web service does not have a select/get endpoint for this entity!"); }
public virtual T Get(T entity) { throw new NotImplementedException("This web service does not have a select/get endpoint for this entity!"); }
public virtual IList<T> GetAll() { throw new NotImplementedException("This web service does not have a select/get endpoint for this entity!"); }
public virtual T GetById(TId id) { throw new NotImplementedException("This web service does not have a select/get endpoint for this entity!"); }
public virtual IList<T> GetList(T entity) { throw new NotImplementedException("This web service does not have a select/get endpoint for this entity!"); }
public virtual T Insert(T entity) { throw new NotImplementedException("This web service does not have an insert/put endpoint for this entity!"); }
public virtual T Update(T entity) { throw new NotImplementedException("This web service does not have an update/post endpoint for this entity!"); }
#endregion
}
RepositoryAdo
public abstract class RepositoryAdo<T, TId> : IRepository<T, TId> where T : class, IEntity<TId>
{
protected IUnitOfWork WorkUnit;
protected Table<T> DataTable;
protected RepositoryAdo(DataContext dataContext) { DataTable = dataContext.GetTable<T>(); }
#region IRepository<T> Members
public virtual T Insert(T entity) { DataTable.InsertOnSubmit(entity); return null; }
public virtual T Update(T entity) { DataTable.InsertOnSubmit(entity); return null; }
public virtual T Delete(T entity) { DataTable.DeleteOnSubmit(entity); return null; }
public virtual IList<T> Get(Expression<Func<T, bool>> predicate) { return DataTable.Where(predicate).ToList(); }
public virtual IList<T> GetAll() { return DataTable.ToList(); }
public virtual T GetById(TId id)
{
// Sidenote: the == operator throws NotSupported Exception!
// 'The Mapping of Interface Member is not supported'
// Use .Equals() instead
return DataTable.Single(e => e.Id.Equals(id));
}
#endregion
}
IUnitOfWork
public interface IUnitOfWork : IDisposable
{
void Commit();
void Rollback();
}
NAMESPACE : Me.Data.Cars
AssetConditionReportRepository
public class AssetConditionReportRepository : RepositoryWebService<CarsWS_CRInfo, string>
{
protected new readonly UnitOfWorkIbeam UnitOfWork;
public AssetConditionReportRepository(UnitOfWorkIbeam unitOfWork) : base(unitOfWork) { UnitOfWork = unitOfWork; }
public override CarsWS_CRInfo Get(CarsWS_CRInfo entity)
{
return GetById(entity.assetID, entity.caseID);
}
public CarsWS_CRInfo GetById(string assetId, string caseId)
{
var response = UnitOfWork.Service.GetAssetConditionReport(UnitOfWork.Credentials, assetId, caseId);
return response;
}
}
UnitOfWorkIbeam
public class UnitOfWorkIbeam : IUnitOfWork
{
#region Properties
internal AssetManagementServiceSoapClient Service { get; private set; }
internal UserCredentialsHeader Credentials { get; private set; }
internal bool IsAllowedToRun = false;
internal string _uid = "";
internal string _pwd = "";
// need to hold on to the delegate so we can add/remove it as needed...
private readonly RemoteCertificateValidationCallback _sslCheck = (sender, certificate, chain, sslPolicyErrors) => true;
#endregion
#region Constructor
public UnitOfWorkIbeam() { SetupServerCertificateValidation(); }
protected UnitOfWorkIbeam(string userName, string password) { SetupServerCertificateValidation(userName, password); }
#endregion
#region Implement IUnitOfWork
public void Commit() { /* No commit on this web service */ }
public void Rollback() { /* No rollback on this web service */ }
#endregion
#region Implement IDisposable
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose() { CleanUpServerCertificateValidation(); }
#endregion
#region Private Helpers
internal void SetupServerCertificateValidation(string userName = null, string password = null)
{
// Setup delegate to ignore SSL cert check...
ServicePointManager.ServerCertificateValidationCallback += _sslCheck; //((sender1, certificate, chain, sslPolicyErrors) => true);
// Setup web service...
Service = new AssetManagementServiceSoapClient();
Credentials = new UserCredentialsHeader { userName = userName ?? Uid, password = password ?? Pwd };
IsAllowedToRun = true;
}
internal void CleanUpServerCertificateValidation()
{
// Setup delegate to ignore SSL cert check...
if (ServicePointManager.ServerCertificateValidationCallback != null)
ServicePointManager.ServerCertificateValidationCallback -= _sslCheck;
// Setup web service...
Service = null;
Credentials = null;
IsAllowedToRun = false;
}
#endregion
}
NAMESPACE : Me.Data.Symitar
Repositories
I am just spinning up this project and don't have any repositories designed as of yet.
UnitOfWorkSymitar
public class UnitOfWorkSymitar : IUnitOfWork
{
#region Properties
internal IPHostEntry IpHostInfo;
internal IPAddress IpAddress;
internal IPEndPoint RemotEndPoint;
internal Socket Sender;
internal byte[] Bytes = new byte[1024];
internal bool IsAllowedToRun = false;
internal string SymServerIp = "192.168.1.1";
internal int SymPort = 11000;
#endregion
#region Constructor
public UnitOfWorkSymitar() { OpenSymitarConnection(); }
protected UnitOfWorkSymitar(string serverIp, int? port) { OpenSymitarConnection(serverIp, port); }
#endregion
#region Implement IUnitOfWork
public void Commit() { /* No commit on this socket connection */ }
public void Rollback() { /* No rollback on this socket connection */ }
#endregion
#region Implement IDisposable
public void Dispose() { CloseSymitarConnection(); }
#endregion
#region Private Helpers
private string OpenSymitarConnection(string serverIp = null, int? port = null)
{
try
{
IpHostInfo = Dns.GetHostEntry(serverIp ?? SymServerIp);
IpAddress = IpHostInfo.AddressList[0];
RemotEndPoint = new IPEndPoint(IpAddress, port ?? SymPort);
Sender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Sender.Connect(RemotEndPoint);
}
catch (Exception ex)
{
return DetermineError(ex);
}
IsAllowedToRun = true;
return "Success!";
}
private string SendMessage(string request)
{
try
{
var msg = Encoding.ASCII.GetBytes(request + "\n");
var bytesSent = Sender.Send(msg);
var bytesRec = Sender.Receive(Bytes);
var response = Encoding.ASCII.GetString(Bytes, 0, bytesRec);
response = response.Replace("\n", "");
return response;
}
catch (Exception ex)
{
return DetermineError(ex);
}
}
private string CloseSymitarConnection()
{
try
{
Sender.Shutdown(SocketShutdown.Both);
Sender.Close();
}
catch (Exception ex)
{
return DetermineError(ex);
}
finally
{
IsAllowedToRun = false;
}
return "Success!";
}
private static string DetermineError(Exception ex)
{
if (ex.GetType() == typeof(ArgumentNullException))
return "Missing Arguments: " + Environment.NewLine + ex.GetFullMessage();
if (ex.GetType() == typeof(SocketException))
return "Socket Error: " + Environment.NewLine + ex.GetFullMessage();
return "Unexpected Error: " + Environment.NewLine + ex.GetFullMessage();
}
#endregion
}
Use of patterns in code
public CaseNote PutData(int userId, string caseId, long status, CaseNoteType noteType, string note)
{
// Map our objects to CARS objects...
...
// package up the web request...
...
// execute the web request...
CarsWS_CaseNote response = null;
using (var uow = new UnitOfWorkIbeam())
{
var caseNotesRepository = new CaseNotesRepository(uow);
response = caseNotesRepository.Insert(userId, caseId, status, noteType, note);
}
// Map the CARS objects back to our objects...
...
// Return our objects...
return myCaseNote;
}
As you can see, each UnitOfWork
(so far) handles the under pinnings of connection, authentication, setup and tear down. I used IDisposable
to ensure that the tear down happens everytime the UnitOfWork
goes out of scope. Now, some of the things I am not happy with:
- Commit/Rollback : Hitting a 3rd party service seems to be an all or nothing proposition. The call can fail, but if something downstream fails I have no way to roll back the earlier calls.
GetById(TId id)
: I went with this because I have found instances where ID is int, string and/or guid. However, it does not handle compound keys! It also seems to clutter up the Repository creation.
All of that said, does anyone see anything I am doing wrong or could do better? I am rewriting almost everything from the ground up and NOW is the time to make sure I have things RIGHT (or right enough to support the future).
I appreciate any feedback ya'll may have - good and bad.