2

I am currently creating a proof-of-concept application that implements the repository pattern. I decided to use the classic ADO.NET and Entity Framework as my sample ORMs. This is currently how my IRepository and IUnitOfWork interfaces are implemented.

public interface IUnitOfWork : IDisposable
{
    IEmployeeRepository Employees { get; }

    int Complete();
}

public interface IRepository<TEntity> where TEntity : class
{
    TEntity Get(int id);
    IEnumerable<TEntity> GetAll();
    IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate);

    TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate);

    void Add(TEntity entity);
    void AddRange(IEnumerable<TEntity> entities);

    void Remove(TEntity entity);
    void RemoveRange(IEnumerable<TEntity> entities);
}

All is well on my Entity Framework implementation for updating objects because it has a built-in ChangeTracker class that checks the states of the objects.

static void Main(string[] args)
{
    var context = new RPContextDbFactory().CreateDbContext(null);

    using (var unitOfWork = new Data.EF.UnitOfWork(context))
    {
        //using Entity Framework
        var employee = unitOfWork.Employees
            .Get(1);
        employee.FirstName = "Foo";

        unitOfWork.Complete(); // <-- Calls DbContext.SaveChanges()
    }
}

My problem is how I can implement the same concept on a classic ADO.NET implementation since it does not have a ChangeTracker class like EF.

static void Main(string[] args)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

    IConfigurationRoot configuration = builder.Build();


    var connectionString = configuration.GetConnectionString("RPContext");

    using (var unitOfWork = new Data.StoredProcedures.UnitOfWork(connectionString))
    {
        //using classic ADO.NET
        var employee = unitOfWork.Employees
            .Get(1);
        employee.FirstName = "Foo";

        unitOfWork.Complete(); //<-- Nothing will happen. 
    }
}

//UnitOfWork implementation for my classic ADO.NET
public class UnitOfWork : IUnitOfWork
{
    public UnitOfWork(string connectionString)
    {
        Employees = new EmployeeRepository(connectionString);
    }

    public IEmployeeRepository Employees { get; private set; }

    public int Complete()
    {
        throw new NotImplementedException();
    }

    public void Dispose()
    {
        throw new NotImplementedException();
    }
}

One of the advice I saw in other sites was to implement a ChangeTracker sort of logic inside the UnitOfWork class of my ADO.NET namespace but I am not really sure how to implement it. How will my UoW know which objects was changed?

Josh Monreal
  • 754
  • 1
  • 9
  • 29
  • fyi [C# had a nice `TransactionScope`](https://stackoverflow.com/a/224702/4648586) to handle 'unit of work'. not to mention EntityFramework actually track changes through `DbContext` -- different context is different unit of work. however, if you are in need to implement one by yourself, you could keep tabs of the original objects. – Bagus Tesa Jun 07 '18 at 02:10
  • I understand that but the application that I am creating is decoupled from any ORM frameworks. As I have mentioned, Entity Framework is not my problem here because DbContext has a ChangeTracker class for keeping track of the state of its objects. The classic ADO.NET ORM does not have this feature which is why I am having problems with updating objects. You mentioned that I could keep tabs of the original objects. How should I implement this "tabs"? Also, how will I know which particular object was changed? – Josh Monreal Jun 07 '18 at 02:56
  • OK, so you want capabilities of an ORM without using one? You have to code it yourself them, just add a property to your classes that mark it as modified and then you know that class has been modified... Transactions are also a possible solution as stated by @Bagus Tesa – Isma Jun 07 '18 at 13:30
  • @Isma: I would have to disagree with you on adding a property to your domain objects. If you have around 50 entities in your application then you would have to go through each of them just to add this property. Not only is that very time-consuming but it also defeats one of the key purpose of the repository pattern which is to decouple your application from any ORM frameworks. Your other application layers should always remain unchanged regardless of the ORM being used. – Josh Monreal Jun 08 '18 at 02:04
  • "If you have around 50 entities in your application then you would have to go through each of them" or you could add a base class. Anyway, that was just an idea. You can also have a list of modified entities in a context class. My point was that you want to have an ORM without using one. So you'll have to code some parts. – Isma Jun 08 '18 at 06:55
  • I think the better approach is to create your own implementation of a Context class. It is way harder than modifying the entities or creating a base class but it still does support the concept of the repository pattern. If you are going to create a base class then you are already making a change in your Domain layer. The idea of the repository pattern is that nothing should change on your application layers except the Data layer regardless of the ORM being used. – Josh Monreal Jun 14 '18 at 03:30

1 Answers1

2

I do not understand the need of using both ADO.NET and EF. EF alone should do.

Anyway, I will recommend NOT to implement UoW yourself for ADO.NET. Its lot of work and error prone and finally you will discover you done all it wrong. If you really want to use UoW, use EF (or NHibernate or else) instead. Why reinvent the wheel? If you decide to implement it yourself, you are actually writing your own ORM which is considered an anti pattern.

If you want to implement UPDATE feature using ADO.NET, then you can simply implement void Update(TEntity entity); method in your IRepository and call it explicitly from calling code. Do not involve IUnitOfWork to do this automatically.

Edit: Replies to some of your comments -

The classic ADO.NET ORM does not have this feature which is why I am having problems with updating objects.

ADO.NET is not an ORM. It is simple and core API to communicate with RDBMS. All ORMs use it internally.

it also defeats one of the key purpose of the repository pattern which is to decouple your application from any ORM frameworks.

No, hiding ORM is not the objective of Repository. Abstracting the database logic is. By doing that, you can mock the Repository and your other code becomes testable. Switching the ORM is quite rare decision. Repository makes that process easy; sure. But still, it is not the responsibility of Repository. Actually, Repository has nothing to do with ORM or not.

Repository pattern must still be implemented for all of them.

It may be; depending on business needs. Full ORMs like EF are itself Repository. So it is design decision whether to add one more abstraction over it. Some do add it; some prefer to use ORM directly. Both approaches have benefits and drawbacks.

Creating an Update method is incorrect because your repository should not concern itself with the semantics of your database.

You have to implement ORM yourself then. Many ORMs are open source. Refer Dapper-Contrib code on Git Hub. It supports limited UoW; good to start. Hope it may help you.

I still insist (from my own experience) not to develop own ORM.

Amit Joshi
  • 15,448
  • 21
  • 77
  • 141
  • It is because I am creating a proof-of-concept application which implements the repository pattern. I am not intending to create a fully-functional, error-free UoW; all I need is a way to create an implementation of the Update method. I just want to clarify with you that this is all about implementing the repository pattern. We should not care whether it is ADO.NET, nHibernate, or another ORM. Repository pattern must still be implemented for all of them. Creating an Update method is incorrect because your repository should not concern itself with the semantics of your database. – Josh Monreal Jun 08 '18 at 02:29
  • The correct way to implement an update regardless of ORM should be like this: var employee = unitOfWork.Employees.Get(1); employee.FirstName = "Foo"; unitOfWork.Complete(); – Josh Monreal Jun 08 '18 at 02:29