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
What you are trying to do is not possible.
You basically have an object of type
a<t>
that you try to cast toa<i>
, and that is not a valid cast, even if ‘t’ implement ‘i’. You need to considera<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 inSecretService
:e.g.
A different approach could be to use
dynamic
but it would defeat the benefit of type-safety, you are getting with your generics.An alternative, if you don’t making an API change, would be to replace
GetRepositoryByName
withGetRepositoryByType
.This removes the
switch
statement and multiple private repository fields in favor of a dictionary keyed by type.You of course would still need the changes as suggested in EsbenB’s answer.
And because you’re getting the strongly typed
IGenericSecretRepository<T>
back, you can use all methods available through that interface.