skip to Main Content

i have 54 Classes (simplified, and anonimized in the example below) with Repos in my C# ASP.NET Core Application that all follow the same structure.
So i decided to make a GenericRepository with interface and explicitly for those 54 i made a generic sub repo called IGenericSecretRepository.
I need a generic function to go from Name to the specified Repository, because i just want to have 1 switch where i convert the name to the specified repository.

Problem is, i cant return IGenericSecretRepository without specifying the type, and also it gives me a casting exception in runtime when doing it like in the example below. (in GetRepositoryByName, on the returns)

Any tips on how i could achieve this in an elegant way?

namespace SecretProject.Repositories
{
    public interface IGenericRepository<T> where T : class
    {
        IEnumerable<T> GetAll();
        T? GetById(int id);
        void Insert(T obj);
        void Update(T obj);
        void Delete(int id);
        void Save();
    }

    public interface IGenericSecretRepository<T> : IGenericRepository<T> where T : SecretBase
    {
        T? GetByPid(string pid);
        T? GetByName(string name);
        void InsertOrUpdate(T obj);
    }

    public class GenericRepository<T> : IGenericRepository<T> where T : class
    {
        private protected readonly SecretProjectDbContext _context;
        private protected DbSet<T> table;

        public GenericRepository(SecretProjectDbContext context) 
        { 
            _context = context;
            table = _context.Set<T>();
        }
        public IEnumerable<T> GetAll()
        {
            return table.ToList();
        }
        public T? GetById(int id)
        {
            return table.Find(id);
        }
        public void Insert(T obj)
        {
            table.Add(obj);
            Save();
        }
        public void Update(T obj)
        {
            table.Attach(obj);
            _context.Entry(obj).State = EntityState.Modified;
            Save();
        }
        public void Delete(int id)
        {
            var existing = table.Find(id);
            if (existing != null)
                table.Remove(existing);
            Save();
        }
        public void Save()
        {
            _context.SaveChanges();
        }
    }

    public class GenericSecretRepository<T> : GenericRepository<T>, IGenericSecretRepository<T> where T : SecretBase
    {
        public GenericSecretRepository(SecretProjectDbContext context) : base(context){ }
        public T? GetByPid(string pid)
        {
            return table.FirstOrDefault(x => x.PID == pid);
        }
        public T? GetByName(string name)
        {
            return table.FirstOrDefault(x => x.Name == name);
        }
        public void InsertOrUpdate(T obj)
        {
            if (GetByPid(obj.PID) != null)
                Update(obj);
            else
                Insert(obj);
        }
        public new void Update(T obj)
        {
            table.Attach(obj);
            _context.Entry(obj).State = EntityState.Modified;

            var existing = GetByPid(obj.PID);
            if (existing != null && !obj.EqualsWithoutID(existing))
            {
                existing.CopyChanges(obj);
                _context.Entry(existing).State = EntityState.Modified;
                Save();
            }
        }
    }
}

namespace SecretProject.Features.Secret
{
    public class SecretService
    {
        private readonly IConfiguration _configuration;
        private readonly IGenericSecretRepository<Secret_View> _SecretViewRepository;
        private readonly IGenericSecretRepository<Secret_PropertyType> _propertyTypeRepository;
        private readonly IGenericSecretRepository<Secret_View_ActivitiesSystemwide> _activitiesSystemwideRepository;
        private readonly IGenericSecretRepository<Secret_View_ActivityTypes> _activityTypesRepository;
        private readonly IGenericSecretRepository<Secret_View_AllArticles> _allArticlesRepository;
        private readonly IGenericSecretRepository<Secret_View_AllProjects> _allProjectsRepository;
        private readonly IGenericSecretRepository<Secret_View_AllProjectsinkldone> _allProjectsinkldoneRepository;

        public SecretService(
            IConfiguration configuration,
            IGenericSecretRepository<Secret_View> SecretViewRepository,
            IGenericSecretRepository<Secret_PropertyType> propertyTypeRepository,
            IGenericSecretRepository<Secret_View_ActivitiesSystemwide> activitiesSystemwideRepository,
            IGenericSecretRepository<Secret_View_ActivityTypes> activityTypesRepository,
            IGenericSecretRepository<Secret_View_AllArticles> allArticlesRepository,
            IGenericSecretRepository<Secret_View_AllProjects> allProjectsRepository,
            IGenericSecretRepository<Secret_View_AllProjectsinkldone> allProjectsinkldoneRepository)
        {
            _configuration = configuration;
            _SecretViewRepository = SecretViewRepository;
            _propertyTypeRepository = propertyTypeRepository;
            _activitiesSystemwideRepository = activitiesSystemwideRepository;
            _activityTypesRepository = activityTypesRepository;
            _allArticlesRepository = allArticlesRepository;
            _allProjectsRepository = allProjectsRepository;
            _allProjectsinkldoneRepository = allProjectsinkldoneRepository;
        }

        public async Task<IEnumerable<SecretBase>> GetView(string name)
        {
            var repo = GetRepositoryByName(name);
            return repo.GetAll();
        }

        public IGenericSecretRepository<SecretBase> GetRepositoryByName(string name)
        {
            switch(name)
            {
                case "ActivitiesSystemwide": return (IGenericSecretRepository<SecretBase>)_activitiesSystemwideRepository; // HERE: InvalidCastException
                case "ActivityTypes": return (IGenericSecretRepository<SecretBase>)_activityTypesRepository;
                case "AllArticles": return (IGenericSecretRepository<SecretBase>)_allArticlesRepository;
                case "AllProjects": return (IGenericSecretRepository<SecretBase>)_allProjectsRepository;
                case "AllProjectsinkldone": return (IGenericSecretRepository<SecretBase>)_allProjectsinkldoneRepository;
            }
            return null;
        }

        public async Task RunViewTask(string viewName)
        {
            var creds = GetCredentials();
            var view = _SecretViewRepository.GetByName(viewName);

            if (view == null) return;

            var xdView = new View()
            {
                PID = view.PID,
                ProjectPID = _configuration.GetValue<string>("Secret:ProjectPID")
            }.ToXML();

            using var systemWebServiceSoapClient = new SystemWebServiceSoapClient(SystemWebServiceSoapClient.EndpointConfiguration.SystemWebServiceSoap12);
            systemWebServiceSoapClient.Open();

            var viewData = await systemWebServiceSoapClient.GetViewDataAsync(creds, xdView);
            systemWebServiceSoapClient.Close();

            XDocument viewDataDoc = XDocument.Parse(viewData.Body.GetViewDataResult);

            var listOfXmlObjects = viewDataDoc.Elements().Elements().Elements();
            var activitiesSystemwideFromSecret = new List<ActivitiesSystemwide>();

            foreach (var xmlObject in listOfXmlObjects)
            {
                var SecretObj = new ActivitiesSystemwide()
                {
                    PID = xmlObject.Attribute("PID")?.Value ?? "",
                    XML = xmlObject.ToString()
                };

                activitiesSystemwideFromSecret.Add(SecretObj);
                _activitiesSystemwideRepository.InsertOrUpdate(SecretObj);
            }

            //check for deleted activities
            var existingActivitiesSystemwide = _activitiesSystemwideRepository.GetAll();
            var activitiesSystemwideToDelete = existingActivitiesSystemwide.Where(x => activitiesSystemwideFromSecret.Where(y => y.PID == x.PID).Count() == 0).ToList();
            foreach (var activitiySystemwideToDelete in activitiesSystemwideToDelete)
                _activitiesSystemwideRepository.Delete(activitiySystemwideToDelete.ID);
        }

    }
}


I tried several solutions but with this example im currently getting this error on runtime on the commented line:

System.InvalidCastException: Unable to cast object of type 'dgOasis.Repositories.GenericInStepRepository`1[dgOasis.Models.InStep_View_ActivitiesSystemwide]' to type 'dgOasis.Repositories.IGenericInStepRepository`1[dgOasis.Models.InStepBase]'.

2

Answers


  1. What you are trying to do is not possible.

    You basically have an object of type a<t> that you try to cast to a<i>, and that is not a valid cast, even if ‘t’ implement ‘i’. You need to consider a<t> as a type on it’s own, and place the abstraction outside the generic.

    One way to go about this is to let your IGenericRepository class implement a non-generic interface and then return that in SecretService:

    e.g.

    interface IRepository
    {
      //Interface worked on by SecretService
    }
    
    
    class IGenericRepository<T> : IRepository  where T : class
    {
        .
        .
        .
    }
    

    A different approach could be to use dynamic but it would defeat the benefit of type-safety, you are getting with your generics.

    Login or Signup to reply.
  2. An alternative, if you don’t making an API change, would be to replace GetRepositoryByName with GetRepositoryByType.

    public IGenericSecretRepository<T> GetRepositoryByType<T>() where T : SecretBase
    {
        return (IGenericSecretRepository<T>)_repos[typeof(T)];
    }
    

    This removes the switch statement and multiple private repository fields in favor of a dictionary keyed by type.

    private readonly Dictionary<Type, IRepository> _repos = new();
    

    You of course would still need the changes as suggested in EsbenB’s answer.

    interface IRepository
    {
    ...
    }
    
    class IGenericRepository<T> : IRepository  where T : class
    {
    ...
    }
    

    And because you’re getting the strongly typed IGenericSecretRepository<T> back, you can use all methods available through that interface.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search