How can I achieve something like the following?
public interface IGenericRepository
{
int id { get; }
T GetById<T>() where T : class
}
public class GenericRepository : IGenericRepository
{
//Some code here
public T GetById<T>(int tid) where T : class
{
return from tbl in dataContext.GetTable<T> where tbl.id == tid select tbl;
}
}
And I would like to use this as follows:
GenericRepository gr = new GenericRepository();
Category cat = gr.GetById<Category>(15);
Of course, in this usage, tbl.id in the GenericRepository gives me an error.
SOLUTION
public class IHasId
{
public int id { get; set; }
}
public interface IGenericRepository
{
int id { get; }
T GetById<T>(int id) where T : IHasId;
}
public class GenericRepository : IGenericRepository
{
public int id
{
get { throw new NotImplementedException(); }
}
public T GetById<T>(int id) where T : IHasId
{
return from tbl in dataContext.GetTable<T> where tbl.id == tid select tbl;
}
}
And apart from these, DON'T forget to define this somewhere in your model:
public partial class Category : IHasId { }
And the usage is:
Repository rep = new Repository();
Category cat = rep.GetById<Category>(15);
There's a few problems here - the first is that the generic type you're matching is a class, but a class doesn't have a property called 'id'. You need to have your Category class implement an interface that exposes an 'id' property:
public interface IIdentity
{
int identity { get; set; }
}
public class Category : IIdentity
{
public int identity{ get; set; }
}
I don't know why you've exposed 'id' as a property on the IGenericRepository interface - surely this is supposed to be a parameter passed to the find method (as indicated by your implementation). You also need to change the restriction on the 'GetById' method from:
where T : class
to something like
where T : IIdentity
using the interface I've suggested above.
public class IHasId
{
public int id { get; set; }
}
public interface IGenericRepository<T>
{
int id { get; }
T GetById(int id);
}
public class GenericRepository<T> : IGenericRepository<T> where T : IHasId
{
public int id
{
get { throw new NotImplementedException(); }
}
public T GetById(int id)
{
return from tbl in dataContext.GetTable<T> where tbl.id == tid select tbl;
}
}
You get this error because you accept every class where T : class. A class don't have that property.
Create an abstract class or interface to make sure that this property exists and change where T : class to where T : IHasIdProperty.
Related
I have a singleton class, holding several list of data. I want to have a function to return one of the list based on the requested data type
public interface IRentable
{
long Id { get; set; }
}
public class Book : IRentable
{
private long _id;
public long Id
{
get { return _id; }
set { _id = value; }
}
public string Name { get; set; }
public int Pages { get; set; }
}
public class DVD : IRentable
{
private long _id;
public long Id
{
get { return _id; }
set { _id = value; }
}
public string Name { get; set; }
public TimeSpan Length { get; set; }
}
public class DBReferenceSingleton
{
private List<Book> _bookList;
private List<DVD> _dvdList;
public IEnumerable<Entity> GetEntities<Entity>() where Entity : IRentable
{
switch(typeof(Entity).Name)
{
case nameof(Book):
return _bookList;
case nameof(DVD):
return _dvdList;
}
return null;
}
This is not working, since a conversion is needed. I wonder if there is any elegant solution (without serialize all elements or implement IConvertible)?
Background: I was looking into the implementation of Repository pattern implementation from Microsoft document website and a couple of YouTube videos. Their base class implementations are bounded with DBContext,
[from Microsoft doc]
public class GenericRepository<TEntity> where TEntity : class
{
internal SchoolContext context;
internal DbSet<TEntity> dbSet;
[from youtube]
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly DbContext Context;
public Repository(DbContext context)
{
Context = context;
}
public IEnumerable<TEntity> GetAll()
{
return Context.Set<TEntity>().ToList();
}
So, this means this base class cannot be used for databases without EntityFramework. I'm trying to remove DBContext in my base class
public class InMemoryRepository<TEntity> : IRepository<TEntity> where TEntity : IRentable
{
private IEnumerable<TEntity> _entities;
public IEnumerable<TEntity> GetAll()
{
if (_entities == null)
{
_entities = DBReferenceSingleton.GetInstance.GetEntities<TEntity>();
}
return _entities;
}
public TEntity Get(long id)
{
GetAll();
if (_entities == null)
{
return default(TEntity);
}
return _entities.FirstOrDefault(t => t.Id == id);
}
(I was wrong in my previous code, as I was using "class" instead of "IRentable" as I thought it will be generic for all classes. After implementing the Get(long id) function. I found I have to have a IRentable interface so I made the changes. And the answer from The Lemon work perfectly.
Thanks
It doesn't look like generics seem the right tool here; not least because Book and DVD don't have a common base class.
I think you should have two methods:
public List<Book> GetBooks() => _bookList;
public List<DVD> GetDvds() => _dvdList;
Have you tried doing a Cast?
public class Book
{
public string Name { get; set; }
public int Pages { get; set; }
}
public class DVD
{
public string Name { get; set; }
public TimeSpan Length { get; set; }
}
public class DBReferenceSingleton
{
private List<Book> _bookList;
private List<DVD> _dvdList;
public IEnumerable<Entity> GetEntities<Entity>() where Entity : class
{
switch(typeof(Entity).Name)
{
case nameof(Book):
return _bookList as List<Entity>;
case nameof(DVD):
return _dvdList as List<Entity>;
}
return null;
}
}
the "as List< Entity >" will return null if the object is the wrong type, or the object as the type if it's the right type. The A = B as C pattern works for inherited types as well, just remember to check for null returned values for cases where your type isn't as well known as in this situation
A side comment on the usefulness of having a generic method in this case: In this method you're forced to set the type of entity each time explicitly, which means your method is functionally non-generic - so you might as well make two explicit methods.
One case where a generic method like the one you have might be more useful, is if book and dvd both inherited from a base class, and you had some follow up methods that needed to operate off a list of . For example, you might end up wanting to do something like this instead in your code:
public class Book : Rentable
{
public int Pages { get; set; }
}
public class DVD : Rentable
{
public TimeSpan Length { get; set; }
}
public class Rentable
{
public string Name { get; set; }
public string borrowedBy { get; set; }
}
public class DBReferenceSingleton
{
private List<Book> _bookList;
private List<DVD> _dvdList;
public enum RentableType { Book, DVD }
public IEnumerable<Rentable> GetEntities(RentableType entityType)
{
switch (entityType)
{
case RentableType.Book:
return _bookList.ToList<Rentable>();
case RentableType.DVD:
return _dvdList.ToList<Rentable>();
default:
throw new NotImplementedException($"Entity {entityType} not supported");
}
return null;
}
}
I've written a code as below. In this code I want to put a constraint on ServiceResult and BaseService classes so that T needs to implement IBaseEntity interface.
Here is the code:
public interface IBaseEntity
{
int Id { get; set; }
}
public class Photo : IBaseEntity
{
public int Id { get; set; }
public float FileSize { get; set; }
public string Url { get; set; }
}
public class ServiceResult<T> where T : class, IBaseEntity, new()
{
public bool Succeed { get; set; }
private T data;
public T Data
{
get
{
if (data == null)
data = new T();
return data;
}
set
{
data = value;
}
}
}
public abstract class BaseService<T> where T : class, IBaseEntity, new()
{
public abstract ServiceResult<List<T>> GetAll();
public abstract ServiceResult<T> GetById(int Id);
}
public class PhotoService : BaseService<Photo>
{
public override ServiceResult<List<Photo>> GetAll()
{
throw new Exception();
}
public override ServiceResult<Photo> GetById(int Id)
{
throw new Exception();
}
}
public class Program
{
public static void Main(string[] args)
{
Console.ReadKey();
}
}
In the code I get the error as below (error refers to GetAll() methods)
Error 3 The type
'System.Collections.Generic.List' cannot be used
as type parameter 'T' in the generic type or method
'FOC.Session04.ServiceResult'. There is no implicit reference
conversion from 'System.Collections.Generic.List'
to 'FOC.Session04.IBaseEntity'. G:\Courses\ASP.NET MVC5\Session4
960803\FOC.Session04\FOC.Session04\Program.cs 55 52 FOC.Session04`
But when I remove the interface constraint IBaseEntity from ServiceResult class and let it remain after BaseService I will get no error and the code compiles without error.
Can anybody explain me why I can't add constraint after ServiceResult class?
What's the reason? Or which part of code need to be changed in order to compile error less in this case?
Thanks all
I think you want rather
public abstract List<ServiceResult<T>> GetAll();
instead of
public abstract ServiceResult<List<T>> GetAll();
List<T> does not even match your constraints (hence the compiler error)
What you really need is for GetAll to return a List of ServiceResults, like so
public interface IBaseEntity
{
int Id { get; set; }
}
public class Photo : IBaseEntity
{
public int Id { get; set; }
public float FileSize { get; set; }
public string Url { get; set; }
}
public class ServiceResult<T> where T : class, IBaseEntity, new()
{
public bool Succeed { get; set; }
private T data;
public T Data
{
get
{
if (data == null)
data = new T();
return data;
}
set
{
data = value;
}
}
}
public abstract class BaseService<T> where T : class, IBaseEntity, new()
{
public abstract List<ServiceResult<T>> GetAll();
public abstract ServiceResult<T> GetById(int Id);
}
public class PhotoService : BaseService<Photo>
{
public override List<ServiceResult<Photo>> GetAll()
{
throw new Exception();
}
public override ServiceResult<Photo> GetById(int Id)
{
throw new Exception();
}
}
public class Program
{
public static void Main(string[] args)
{
Console.ReadKey();
}
}
I made a look at the source again and understood what is going on.
Putting IBaseEntity constraint on BaseService class has no problem. Because T represents a single class here (PhotoService : BaseService<Photo>). So T is Photo and Photo implements IBaseEntity.
But for GetAll() method in BaseService class the return type is ServiceResult<List<T>>. Therefore in ServiceResult class T will be something like List<Photo> and List<Photo> doesn't implement IBaseEntity. Therefore it raises an error.
Removing IBaseEntity constraint from ServiceResult class solves the problem.
I'm following this site: http://deviq.com/repository-pattern/
Which has an example of a repository pattern using a DB context. I'm trying to implement this generic Repository class with a list (I want the Repository class. That's a requirement of mine). However, I'm having issues with the Find method.
public class Repository<T> : IRepository<T> where T : class
{
private List<T> context;
virtual public T Find(int id)
{
// I can't figure out a way to make this work with the list of a generic type
}
}
Is this even possible to make a predicate in the List.Find() with just an ID parameter? I'm guessing no, but what are options?
Another option, if you can't control the type of T so as to apply an interface, is to force your implementers to do the hard work.
public abstract class Repository<T> : IRepository<T> where T : class
{
private List<T> context;
public virtual public T Find(int id)
{
return context.FirstOrDefault(x => GetId(x) == id);
}
public abstract int GetId(T entity);
}
an example implementation may be
// entity
public class Stooge
{
public Stooges MoronInQuestion {get;set;}
public double MoeEnragementFactor {get;set;}
public void PloinkEyes() { /*snip*/ }
public void Slap() { /*snip*/ }
public void Punch() { /*snip*/ }
// etc
}
// enum for an Id? It's not that crazy, sometimes
public enum Stooges
{
Moe = 1,
Larry = 2,
Curly = 3,
Shemp = 4,
Joe = 5,
/* nobody likes Joe DeRita */
//CurlyJoe = -1,
}
// implementation
public class StoogeRepository : IRepository<Stooge>
{
public override int GetId(Stooge entity)
{
if(entity == null)
throw new WOOWOOWOOException();
return (int)entity.MoronInQuestion;
}
}
You can declare that T has an Id property with something like this:
public interface IEntity
{
int Id { get; }
}
public class Repository<T> : IRepository<T> where T : class, IEntity
{
private List<T> context;
virtual public T Find(int id)
{
return context.SingleOrDefault(p => p.Id == id);
}
}
Is it possible to create generic restriction in C# using where to select only classes, who have Field with some name.
for example, I have AbstractService<T>
and I have a method IEnumerable<T> ProvideData(userId);
inside provide data I should select only instances with the same user bla-bla-bla.Where(d => d.UserId == userId). But d.UserId could not be resolved. How it possible to resolve this?
IMPORTANT: I can't inherit T from class or interface, which have UserID field.
An interface is what your are looking for:
public interface IWithSomeField
{
int UserId { get; set; }
}
public class SomeGenericClasss<T>
: where T : IWithSomeField
{
}
public class ClassA : IWithSomeField // Can be used in SomeGenericClass
{
int UserId { get; set; }
}
public class ClassB // Can't be used in SomeGenericClass
{
}
[Edit] As you edited your question to state you cannot change class to implement an interface, here is some alternatives, but none relies on generic constraint :
Check the type in the constructor :
code :
public class SomeClass<T>{
public SomeClass<T>()
{
var tType = typeof(T);
if(tType.GetProperty("UserId") == null) throw new InvalidOperationException();
}
}
Use code contract invariant (not sure about the syntax) :
code :
public class SomeClass<T>{
[ContractInvariantMethod]
private void THaveUserID()
{
Contract.Invariant(typeof(T).GetProperty("UserId") != null);
}
}
Extend existing classes with partial classes
If your source classes are generated, you can cheat. I used this technique with lots of Web References having the same kind of parameter objects
Imagine the Web references produced this proxy code :
namespace WebServiceA {
public class ClassA {
public int UserId { get; set; }
}
}
namespace WebServiceB {
public partial class ClassB {
public int UserId { get; set; }
}
}
You can wrap them using in your own code:
public interface IWithUserId
{
public int UserId { get; set; }
}
public partial class ClassA : IWithUserId
{
}
public partial class ClassB : IWithUserId
{
}
then, for your service, you can instantiate AbstractService for any of the Class of the several web services :
public class AbstractService<T> where T : IWithUserId
{
}
This technique works great but only applies when you can extend class in the same project because of the partial keyword trick.
How can I achieve something like the following example?
public interface IGenericRepository {
int id { get; }
T GetById<T>() where T : class
}
public class GenericRepository : IGenericRepository {
//Some code here
public T GetById<T>(int tid) where T : class
{
return from tbl in dataContext.GetTable<T> where tbl.id == tid select tbl;
}
}
and I would like to use this as follows:
GenericRepository gr = new GenericRepository();
Category cat = gr.GetById<Category>(15);
Of course this code does not work since if I define T : class then the tbl.id part won't work. But of course, there should be a way to realize this.
UPDATE
Considering driis' answer, I am doing the following, but I still can't get this working:
public interface IEntity
{
int id { get; }
}
public interface IGenericRepository : IEntity
{
T GetById<T>(int id);
}
public class GenericRepository : IGenericRepository
{
public T GetById<T>(int id) {
return from tbl in dataContext.GetTable<T> where tbl.id == tid select tbl;
}
}
At this point tbl.id works, but dataContext.GetTable<T> is giving me a error.
You can constrain T to be a type that contains an ID, you will likely want an interface for it:
public interface IEntity
{
int Id { get; }
}
Then declare your generic method as:
public IQueryable<T> GetById<T>(int tid) where T : IEntity
{
return from tbl in dataContext.GetTable<T> where tbl.id == tid select tbl;
}
Of course you will need to implement IEntity for all entities. I am guessing you are using Linq2Sql or similar, in which case you can just make a partial class definition that includes the interface implementation.