Search code examples
c#genericsdomain-driven-designabstract-class

C# Abstract Method to enforce update method


In our code base we have a base model (PersistenceEntity) that handles any models that are persisted to storage via a generic repository. This works well for fetching, adding and deleting. The area I want to improve is our update method to be encapsulated in the repository rather than fetching the object in the service layer, manipulating it (in whatever fashion the developer sees fit) and then saving it.

The repository update method should internally load the model, call the update method on the object and then save it (or call the AddOrUpdate extension).

To do this, I was thinking I could add an abstract method on the base class to enforce the developer to implement the update in the model rather than setting the properties in another layer.

public abstract T Update<T>(T existing) where T : PersistenceEntity;

So this would make the developer write a model that would like this:

public class MyClass : PersistenceEntity 
{
        public override MyClass Update<MyClass>(MyClass existing)
        {
            existing.SomeProperty = SomeProperty;
            existing.SomeProperty2 = SomeProperty2;
            return existing;
        }
}

But when I implement it in this way, the compiler complains as the it thinks MyClass is the name of T and not the concrete class. I think I'm missing something obvious here, but I just can't see it... Any suggestions would be appreciated.


Solution

  • You can avoid hiding a class by type parameter by making a PersistenceEntity class generic itself

    public abstract class PersistenceEntity<T> where T : PersistenceEntity<T>
    {
        public abstract T Update(T existing);
    }
    

    It means self-referencing generic constraint, because every class inheriting PersistenceEntity should update an existing instance of itself type.

    The implementation for MyClass would be the following:

    public class MyClass : PersistenceEntity<MyClass>
    {
        public override MyClass Update(MyClass existing)
        {
            existing.SomeProperty = SomeProperty;
            existing.SomeProperty2 = SomeProperty2;
            return existing;
        }
    }
    

    Another option is to create an invariant interface, which incapsulates the Update method (if you aren't allowed to make PersistenceEntity class generic)

    public interface IUpdate<T> where T : PersistenceEntity
    {
        T Update(T existing);
    }
    

    And then implement it

    public class MyClass : PersistenceEntity, IUpdate<MyClass>
    {
        public MyClass Update(MyClass existing)
        {
            existing.SomeProperty = SomeProperty;
            existing.SomeProperty2 = SomeProperty2;
            return existing;
        }
    }