BrilBroeder
BrilBroeder

Reputation: 1579

Wrong extension-method gets called

I created the following extension methods in a .Net 5 webapi project :

public static class QueryableExtensions
{
    public static IOrderedQueryable<TSource> Filter<TSource>(this IOrderedQueryable<TSource> query, IEnumerable<string> filter) where TSource : IModel
    {
        throw new NotImplementedException();
    }

    public static IOrderedQueryable<ApplicationUser> Filter(this IOrderedQueryable<ApplicationUser> query, IEnumerable<string> filter)
    {
        return query
            .Where(x => filter.Any(str => !string.IsNullOrEmpty(x.Email) && x.Email.Contains(str, StringComparison.CurrentCultureIgnoreCase)))
            .OrderBy(x => x.Id);
    }
}

The generic one is just a 'placeholder'. I use these methods in some service classes in a service-repository pattern.

The base service class :

public class DataServiceBase<T> : IDataService<T> where T : class, IModel, new()
{
    public IValidationDictionary ValidationDictionary { get; }
    protected readonly IRepository<T> Repo;

    public DataServiceBase(IRepository<T> repo, IValidationDictionary validationDictionary)
    {
        Repo = repo;
        ValidationDictionary = validationDictionary;
    }

    public virtual IOrderedQueryable<T> Read(ApplicationContext applicationContext)
    {
        return Repo.Read().OrderBy(x => x.Id);
    }

    public virtual IOrderedQueryable<T> Read(ApplicationContext applicationContext, ReadListSettings readListSettings)
    {
        var query = Read(applicationContext);
        if (readListSettings.Filter != null)
        {
            // HERE THE EXTENSIONS METHOD IS USED
            query = query.Filter(readListSettings.Filter.Process());
            // HERE THE EXTENSIONS METHOD IS USED
        }
        return query;
    }
}

And a specific one :

public partial class ApplicationUserService : DataServiceBase<ApplicationUser>
{
    public ApplicationUserService(
        IRepository<ApplicationUser> repo,
        IValidationDictionary validationDictionary
    ) : base(repo, validationDictionary)    {}

    public override IOrderedQueryable<ApplicationUser> Read(ApplicationContext applicationContext)
    {
        return Repo
            .Read()
            .Include(x => x.User2UserGroups)
            .ThenInclude(x => x.UserGroup)
            .ThenInclude(x => x.Tenant)
            .OrderBy(x => x.Email);
    }
}

I would expect that when calling ApplicationUserService.Read(applicationcontext, readlistsettings) the specific version of the extensionmethod is called. I checked the type and the generic method gets the correct class as its TSource.

I tried adding the following to my AppUserService, but still the TSource-version gets called.

public override IOrderedQueryable<ApplicationUser> Read(ApplicationContext applicationContext, ReadListSettings readListSettings)
{
    return base.Read(applicationContext, readListSettings);
}

Any suggestions ?

Upvotes: 0

Views: 120

Answers (1)

Dennis
Dennis

Reputation: 37800

Extension methods are not about polymorphism. Given these models and settings:

    public interface IModel
    {
        public int Id { get; set; }
    }

    public class ApplicationUser : IModel
    {
        public int Id { get; set; }

        public string Email { get; set; }
    }

    public class ReadListSettings
    {
        public IEnumerable<string> Filter { get; set; }
    }

your custom filtering could be implemented like this:

    public abstract class DataServiceBase<T>
        where T : IModel
    {
        public IOrderedQueryable<T> Read()
        {
            // gets queryable from repository somehow
            return Enumerable.Empty<T>().AsQueryable().OrderBy(x => x.Id);
        }

        public IOrderedQueryable<T> Read(ReadListSettings readListSettings)
        {
            var query = Read();
            if (readListSettings.Filter != null)
            {
                // use custom filtering in descendatns here
                query = Filter(query, readListSettings.Filter);
            }

            return query;
        }

        protected virtual IOrderedQueryable<T> Filter(IOrderedQueryable<T> query, IEnumerable<string> filter) => query;
    }

    public partial class ApplicationUserService : DataServiceBase<ApplicationUser>
    {
        protected override IOrderedQueryable<ApplicationUser> Filter(IOrderedQueryable<ApplicationUser> query, IEnumerable<string> filter)
        {
            return query
                .Where(x => filter.Any(str => !string.IsNullOrEmpty(x.Email) && x.Email.Contains(str, StringComparison.CurrentCultureIgnoreCase)))
                .OrderBy(x => x.Id);
        }
    }

(I've simplified your types, and left relevant code only)

Upvotes: 2

Related Questions