1
votes

I am trying to create a generic repository with a generic base class.

Fo rmy base class I have;

public abstract class Entity<T> : IEntity<T>
{
    public abstract T Id { get; set; }

    [Column("IsArchived")]
    public bool? Archived { get; set; }
}

with the interface

public interface IEntity<T>
{
    T Id { get; set; }
    bool? Archived { get; set; }
}

I then want to add an extension method to the Generic repository, where by it either adds or delete based on whether the T.Id is in the default state or not. My method is as follows;

    public static TContext Attach<T, TKey, TContext>(this TContext context, T entity)
        where T : class, IEntity<TKey>
        where TContext : BaseDataContext
    {
        if (EqualityComparer<T>.Default.Equals(entity.Id, default(TKey)))
        {
            context.Set<T>().Add(entity);
        }
        else
        {
            context.Entry(entity).State = EntityState.Modified;
        }
        return context;
    }

I then call want to call the extension method from the repository i.e.

    public async Task<Entity<T>> SaveAsync<T, TKey>(Entity<T> entity, string userName) where T : class, IEntity<TKey>
    {
        this.Attach(entity);
        await this.SaveChangesAsync();
        return entity;
    }

where i get the error

Error 9 Using the generic method 'Repository.Extensions.Attach(TContext, T)' requires 3 type arguments

This works fine when the base class is not a generic type, i.e. int, what o I need to adjust to fix this please?

1
There is nothing you can adjust. The compiler cannot derive TKey argument, and unfortunately in such case it requires you to specify ALL generic type arguments. - Ivan Stoev

1 Answers

1
votes

As I mentioned in the comments, there is nothing you can do with this design. The compiler infers TContext from the context argument, T from the entity argument, but there is no argument that can be used to infer TKey. Note that T might implement IEntity<int>, IEntity<string> etc. at the same time, so although technically it probably should be able to derive that from the concrete class you are using, currently it doesn't, so you have to live with that.

The workaround could be to eliminate TKey by defining a non generic interface like this:

Model:

public interface IEntity
{
    bool? Archived { get; set; }
    bool IsNew { get; }
}

public interface IEntity<T> : IEntity
{
    T Id { get; set; }
}

public abstract class Entity<T> : IEntity<T>
{
    public abstract T Id { get; set; }

    [Column("IsArchived")]
    public bool? Archived { get; set; }

    public virtual bool IsNew { get { return EqualityComparer<T>.Default.Equals(Id, default(T)); } }
}

Extension method:

public static TContext Attach<T, TContext>(this TContext context, T entity)
        where T : class, IEntity
        where TContext : DbContext
{
    if (entity.IsNew)
    {
        context.Set<T>().Add(entity);
    }
    else
    {
        context.Entry(entity).State = EntityState.Modified;
    }
    return context;
}