0

I am writing a generic repository for C# and Entity Framework and I am trying to get this code to work without adding a whole bunch of extra interfaces.

Essentially, I have a class called HavePrimaryKey<TKey> which ALL the database entities inherit from:

public class MemberInfo : HavePrimaryKey<int>

As you can see, they can define what type of primary key the class uses.

This allows me to do:

public interface IDbGenericIdRepository<T, TKey> : IDbGenericRepository<T>
    where T : HavePrimaryKey<TKey>

However, this means I need to define EACH and EVERY key when I create the repository:

IDbGenericIdRepository<MemberInfo, int>

When, in reality, it would be much nicer to be able to have the compiler IMPLY the second type from the fact the first type MUST inherit from the 2nd type:

IDbGenericIdRepository<MemberInfo>

e.g. in the above case, as MemberInfo inherits from HavePrimaryKey<int>, then it HOPEFULLY will IMPLICITLY pick up that it needs an int... but instead it simply moans and says 'requires 2 type arguments'

ONE way to fix this is to use a different repository for each type:

public interface IDbGenericIntIdRepository<T> : IDbGenericRepository<T>
    where T : HavePrimaryKey<int>

public interface IDbGenericStrIdRepository<T> : IDbGenericRepository<T>
    where T : HavePrimaryKey<string>

etc... but I am hoping there might be a less brute force way to manage this?

Any ideas?

Many thanks,

Phil.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
philkills
  • 47
  • 7
  • 2
    The compiler cant read your mind unfortunately. However, the main problem here is you are using a generic repository over EF. Its kind of like running away from the circus to join the orphanage – TheGeneral Aug 04 '20 at 22:56
  • In your `HavePrimaryKey` class, do you need it to be `T` or could you use `Type`? i.e. nowhere in your code do you do `typeof(T)`? – JohanP Aug 04 '20 at 23:05
  • How exactly are you going to use the HavePrimaryKey interface? – insane_developer Aug 04 '20 at 23:55
  • Can you share your `HavePrimaryKey` class? Also, just to confirm whether you are using `EntityFramework` or `Entity Framework Core`. – Ankit Aug 05 '20 at 02:30
  • Yeah I would just do an interface, not a base class like this. Although as people above pointed out a generic repo over EF is a bit of an anti-pattern – zaitsman Aug 05 '20 at 05:13

1 Answers1

0

I understand your thought process, you interfaces are very similar to something I've tried in the past. This guide sums it up though:

Generic Interfaces - Generic classes can implement generic interfaces or closed constructed interfaces as long as the class parameter list supplies all arguments required by the interface

Its all good for them to say that, but in practice it makes sense... Using your definition:

public interface IDbGenericIdRepository<T, TKey> : IDbGenericRepository<T>
    where T : HavePrimaryKey<TKey>

Then we are explicitly defining the Generic condition of T on another generic condition TKey. That means that we need to explicitly define the type argument TKey for the compiler to be able to check that the type of T is valid at all.

We have created a dependency here, TKey and T are not the same type, T however must implement a version of HavePrimaryKey, this interface doesn't care specifically what the actual type of TKey is, only that the class that implements the interface MUST define it.

You still want to know why though don't you...

Basically where Interface inheritance is concerned, we need to be aware that a class that implements one (or more) interfaces can actually present a different implementation for each member of each interface and a different implementation for the class itself!

So now we'll introduce a simple implementation of HavePrimaryKey<TKey> and some example classes that implement this interface in different ways, all are valid in the eys of the compiler.

public interface HavePrimaryKey<TKey>
{
    T PrimaryKey { get; }
}

public class MemberInfoInt : HavePrimaryKey<int>
{
    public int PrimaryKey { get; }
}

public class MemberInfoString : HavePrimaryKey<string>
{
    public int PrimaryKey { get; }
    string HavePrimaryKey<string> PrimaryKey { get; }
}

Another valid implementation, however absurd is this:

public class MemberInfo3 : HavePrimaryKey<int>, HavePrimaryKey<Guid>
{
    public string PrimaryKey { get; }
    int HavePrimaryKey<int>.PrimaryKey { get; }
    Guid HavePrimaryKey<Guid>.PrimaryKey { get; }
}

So now if we try to implement this using your preferred notation, we can start to see the dilemma: Note, this is not valid

public class Repo : IDbGenericIdRepository<MemberInfo3> ...

In this instance the compiler tries to evaluate typeof(MemberInfo3) == HavePrimaryKey<?> and it fails there, it cant even compile the expression that is supposed to be used to validate the first expression.

There is no way (other than the one legal way we already know) to tell the compiler which specific type implementation we intend to use to validate that MemberInfo3 is valid to use for a condition for T.


C# is strongly typed, that's what we all love (or love to hate) about it, so where there is the possibility of ambiguity, it is in our nature to force and expect a strongly typed definition for our types and classes.

That is why you MUST provide all the generic type arguments from the inherited interfaces.

Chris Schaller
  • 13,704
  • 3
  • 43
  • 81