Shekhar Pankaj
Shekhar Pankaj

Reputation: 9125

C# : Extending Generic class

partial class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
}

My generic repository implements a common set of methods for TEntity like

public TEntity Get(int id)
{
    return _context.Set<TEntity>()
        .Find(id);
}

public TEntity Get(Expression<Func<TEntity, bool>> predicate)
{
    return _context.Set<TEntity>()
}

which I can access like

Repository<User>().Get();

Many repositories does the same set of operation, so it is beneficial but now I want to extend Repository<User> to support some additional behavior.

partial class Repository<User> : IRepository<User> 
{
    public user DoMagicFunction()
    {
    }
}

so that I can use the repository like

Repository<User>().DoMagicFunction();

how can I extend the same generic class for Some Tentity to extend new behaviour instead of modifying it.

I could have done the same like creating another UserRepository to support new feature, but the accessor would become

UserRepository.DoMagicFunction();

but I want it to be like

Repository<User>().DoMagicFunction();

Upvotes: 11

Views: 17599

Answers (5)

Rivon
Rivon

Reputation: 584

Extension method is a best choice for this case.

Note: I have not checked but you should check Dependency Injection still works well as normal.

You can use below code for testing:

public class Employee
{
}

public class User
{
}

public interface IRepo<TEntity> where TEntity : class
{
    TEntity Get(int id);
    DbSet<TEntity> Get(Expression<Func<TEntity, bool>> predicate);
    DbContext GetContext();
}

public class Repo<TEntity> : IRepo<TEntity> where TEntity : class
{
    DbContext _context;
    public TEntity Get(int id)
    {
        return _context.Set<TEntity>()
                       .Find(id);
    }

    public DbSet<TEntity> Get(Expression<Func<TEntity, bool>> predicate)
    {
        return _context.Set<TEntity>();
    }

    public DbContext GetContext()
    {
        return _context;
    }
}

public static class RepoExtensions
{
    public static ChangeTracker DoMagic(this Repo<User> userRepo)
    {
        return userRepo.GetContext().ChangeTracker;
    }
}

public static class Test
{
    public static void DoTest()
    {
        Repo<User> repoUser = new Repo<User>();
        repoUser.DoMagic();

        Repo<Employee> repoEmployee = new Repo<Employee>();
        //repoEmployee.DoMagic();
    }
}

Upvotes: 1

QuantumHive
QuantumHive

Reputation: 5683

C# has a language feature called Extension Methods, you probably are using them from the .NET framework without knowing (e.g. the linq extensions methods). It's common to extend your classes or even your interfaces with extension methods without breaking the functionality of your code. Here is an example for your case.

Suppose you have a generic IRepository interface:

public interface IRepository<TEntity> where TEntity : class, IEntity
{
    IQueryable<TEntity> Entities { get; }
}

This interface adheres to the SOLID principles, especially the O and I principle.

Now suppose IEntity looks like this:

public interface IEntity
{
    int Id { get; }
}

Now you could perfectly imagine an often reusable extension method like this:

public static class RepositoryExtensions
{
    // similar to your MagicFunction
    public static TEntity GetById<TEntity>(this IRepository<TEntity> repository, int id)
         where TEntity : class, IEntity
    {
        return repository.Entities.Single(entity => entity.Id == id);
    }
}

In a similar manner you could also extend your Repository class

public static class RepositoryExtensions
{
    public static TEntity GenericMagicFunction<TEntity>(this Repository<TEntity> repository)
    {
         //do some stuff
    }
}

You can now consume that like this:

var repository = new Repository<User>();
var user = repository.GenericMagicFunction();

You could also limit your extension method:

public static class RepositoryExtensions
{
    public static User DoMagicFunction(this Repository<User> repository)
    {
         //do some stuff
    }
}

But doing this will defeat it's purpose, you could rather just implement this in the Repository<User> class.

If your system and architecture uses Dependency Injection, you're probably injecting an IRepository<User> to your consuming classes. So the first or second extension method examples I've provided would make the most sense.

Upvotes: 4

Peter Morris
Peter Morris

Reputation: 23224

Extension methods are not the way to go, because the code that implements the method can only access public/internal members of the class they extend and you are likely to want your repository's DataContext to be private.

In my opinion, your approach needs to be changed slightly.

What if in the future you want to add a Delete method to your generic repository, but you have some entities that should never be deleted? You'll end up with an instance of a repository for something like PurchaseOrder that you'll either have to remember to never call delete on or you will have to create a descendant of Repository<T> that throws an InvalidOperationException if called. Both of which are poor implementations.

Instead, you should delete your IRepository<T> interface completely. Keep your Repository<T> class, but explicitly define a repository interface for every entity that only has the methods you require.

public class Repository<TKey, TEntity>......
{
  public TEntity Get<TEntity>(TKey key)....
  public void Delete(TEntity instance)....
  ...etc...
}

public interface IPurchaseOrderRepository {
  PurchaseOrder Get(int orderNumber);
  // Note: No delete is exposed
}

MyDependencyInjection.Register<IPurchaseOrderRepository, Repository<PurchaseOrder, int>>();

When you need additional methods on your repository you add them to your IPurchaseOrderRepository and create a descendant of Repository<T>

public interface IPurchaseOrderRepository {
  PurchaseOrder Get(int orderNumber);
  void DoSomethingElse(int orderNumber);
}

public class PurchaseOrderRepository: Repository<PurchaseOrder, int> {
  public void DoSomethingElse(int orderNumber) {.......}
}


MyDependencyInjection.Register<IPurchaseOrderRepository, PurchaseOrderRepository>();

Upvotes: 1

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476557

You can use an extension method:

public static class ExtensionMethods {

    public static User DoMagicFunction(this Repository<User> repository) {
        // some magic
        return null; //or another user
    } 

}

This will thus add the function in a syntactically nice way to Repository<User> objects.

In case you want to support it not only for Users, but for subclasses of Users as well, you can make the function generic:

public static class ExtensionMethods {

    public static TEntity DoMagicFunction<TEntity>(this Repository<TEntity> repository)
        where TEntity : User {
        // some magic
        return null; //or another TEntity
    } 

}

Upvotes: 12

Jamiec
Jamiec

Reputation: 136074

If you want to extend any repository you can do it like this.

public static class RepositoryExtension
{
    public static void MagicMethod<TEntity>(this IRepository<TEntity> repo) where TEntity: class
    {
        ....
    }
}

For a specific repository (eg User repository) you can use a similar process

public static class RepositoryExtension
{
    public static void MagicMethod(this IRepository<User> repo) 
    {
        ....
    }
}

Upvotes: 1

Related Questions