Generics in DBContext using Entity Framework [duplicate] - c#

This question already has answers here:
The type must be a reference type in order to use it as parameter 'T' in the generic type or method
(3 answers)
Closed 6 years ago.
I have an interface IMstTuver which is implemented by the MstTuver class. IMstTuver contains MaxVersion and Agenttype parameters.
public class GetTable<T> where T : IMstTuver
{
public IMstTuver GetEntities(DbContext context, string agenttype)
{
long maxVersion = context.Set<T>().Max(x => x.MaxVersion);
IMstTuver mstTuver = context.Set<T>()
.Where(x => x.MaxVersion == maxVersion &&
x.AgentType == agenttype)
.FirstOrDefault();
return mstTuver;
}
}
In my class:
table.GetEntities(MyDbContext, "MSMA") as MstTuver;
I am getting an error
The type 'T' must be a reference type in order to use it as parameter 'TEntity' in the generic type or method 'System.Data.Entity.DbSet'
Please help.

Set<T> has a generic constraint that T must be a reference type, which means that your class needs to have one too. The fix is to apply the following modification to all generic classes and/or methods up the call chain:
public class GetTable<T> where T : class, IMstTuver

You can declare T as a reference type by adding the class type-constraint to your method:
public class GetTable<T> where T : class, IMstTuver
{
//...

I have done something like this in the past and found it easier to put the generic and constraint on the methods than the type. This will allow the same repository type to serve multiple entity types (as long as they meet the constraints).
Example:
public class GetTable
{
public T GetEntities<T>(DbContext context, string agenttype)where T : class, IMstTuver
{
long maxVersion = context.Set<T>().Max(x => x.MaxVersion);
IMstTuver mstTuver = context.Set<T>().Where(x => x.MaxVersion == maxVersion && x.AgentType == agenttype).FirstOrDefault();
return mstTuver;
}
}
In the future when you are returning a generic as a generic from an existing library you can check the constraints on the method you are calling and implement those same constraints. In this case the Set<T> method is constrained to class. You can then add additional non conflicting constraints as you see fit.
DbContext.Set Method
public virtual DbSet<TEntity> Set<TEntity>() where TEntity : class

Related

Resolving a list of classes that implement generic interface and abstract class with Autofac

I am trying to resolve strategy pattern using a factory. This factory generates an open generic interface. It depends on an IEnumerable of that generic interface. I can obtain an IEnumerable of non-generic to work, but with generics i get an empty list.
I can also resolve the class directly, but not the list.
An additional caveat is that we could have potentially unlimited Repositories, so registering them individually would be painful.
I've tried these ways to register with Autofac
var dataAccess = Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(dataAccess).AsClosedTypesOf(typeof(Repositories.IRepository<>));
builder.RegisterAssemblyTypes(dataAccess)
.Where(t => IsAssignableToGenericType(t, typeof(Repositories.IRepository<>)) && !t.IsAbstract && !t.IsInterface)
.AsImplementedInterfaces();
builder.RegisterAssemblyTypes(dataAccess)
.Where(t => IsAssignableToGenericType(t, typeof(Repositories.IRepository<>)) && !t.IsAbstract && !t.IsInterface);
public interface IRepository<T> where T : BaseProcessorType
{
Task Add(T data);
}
public abstract class BaseRepository<T> : IRepository<T> where T : BaseProcessorType
{
public async Task Add(T data)
{
// something
}
}
public class ActivityRepository : BaseRepository<Activity>, IRepository<Activity>
{
public ActivityRepository() : base()
{
}
public override async Task Add(Activity data)
{
// override
}
}
Then I would like to to resolve
var lol = something.Resolve<IEnumerable<Repositories.IRepository<BaseProcessorType>>>();
But unfortunately this returns an empty list of IRepositories.
Let's forget about Autofac and let's try to get the collection with pure C#
IEnumerable<IRepository<BaseProcessorType>> l = new IRepository<BaseProcessorType>[] {
new ActivityRepository()
};
with the code sample the compiler throw an error
Error CS0266 - Cannot implicitly convert typeActivityRepository to IRepository<BaseProcessorType>. An explicit conversion exists (are you missing a cast?)
The main error is that ActivityRepository is not convertible to IRepository<BaseProcessorType>. In order to allow this cast you have to make the T argument covariant by using the out keyword
public interface IRepository<out T> where T : BaseProcessorType
{}
But by doing so you can't have a method with a T parameter
Error CS1961 Invalid variance: The type parameter T must be contravariantly valid on IRepository<T>.Add(T). T is covariant.
To understand why it is forbidden let's see this code sample :
IRepository<BaseProcessorType> r = new Activity1Repository();
r.Add(new Activity2());
In this code sample r works with Activity1 but you want to add an Activity2 and Activity1 is not Activity2.
One solution would be not to use T as a type parameter but use BaseProcessorType
public interface IRepository<out T> where T : BaseProcessorType
{
Task Add(BaseProcessorType data);
}
This way the pure C# solution is valid.
In order to resolve a IEnumerable<IRepository<BaseProcessorType>> you need to register your types as IRepository<BaseProcessorType>.
builder.RegisterAssemblyTypes(dataAccess)
.As(typeof(IRepository<BaseProcessorType>));

Pass Entity Framework type to generic method

In my project using Entity Framework, I have a bunch of functions that look almost exactly alike, so I want to created a generic method they call:
private IHttpActionResult GetData<TEntity>(DbSet<TEntity> data)
The problem I'm having is that the data parameter is saying TEntity has to be a reference type to work, but the type of the entity comes from auto-generated code that doesn't have any base class that I can constrain via a where clause on the method definition.
I'd basically want to call it by getting a context and passing the table in like so:
using (var context = new DataModel.MyEntities()) {
GetData(context.Lab_SubSpace_Contact);
}
To expand on #Igor's answer, you don't have to pass the DbSet<TEntity>, you can also get that dynamically through the type parameter:
private IHttpActionResult GetData<TEntity>() where TEntity : class
{
using (var context = new YourContext())
{
var dbSet = context.Set<TEntity>();
}
}
You do not need a base class, you only have to specify a constraint that it has to be a class (not a struct). This can be done with where TEntity : class
Constraints on Type Parameters
where T : class : The type argument must be a reference type; this applies also to any class, interface, delegate, or array type.
Modified code
private IHttpActionResult GetData<TEntity>(DbSet<TEntity> data) where TEntity : class

Error assigning class implementing generic interface to generic variable [duplicate]

This question already has answers here:
Generics and casting - cannot cast inherited class to base class
(3 answers)
Closed 5 years ago.
With reference to InvalidCastException on Generics, I have adapted the given answer for my own application. As an example:
public interface IData
{
string getValue();
}
public interface IController<in T> where T : IData
{
string formString(T data);
}
public class DataImplA : IData
{
public string getValue()
{
return "DataImplA";
}
}
public class ControllerImplA : IController<DataImplA>
{
public string formString(DataImplA data)
{
return "ControllerImplA, " + data.getvalue();
}
}
public class Program
{
private static IController<IData> controller;
public static void main(string[] args)
{
// Line with error
Program.controller = (IController<IData>)new ControllerImplA();
Console.WriteLine(Program.controller.formString(new DataImplA()));
}
}
During runtime, the indicated line will trigger
InvalidCastException: Unable to cast object of type 'ControllerImplA' to type 'IController`1[IData].
I tried changing to Program.controller = (IController<IData>)new ControllerImplA<DataImplA>(); but this results in the compilation error The non-generic type 'ControllerImplA' cannot be used with type arguments
I then tried changing the class definition to public class ControllerImplA<DataImplA> : IController<DataImplA> but then the new compilation error is:
The type 'DataImplA' cannot be used as type parameter 'T' in the generic type or method 'IController T'. There is no boxing conversion or type parameter conversion from 'DataImplA' to 'IData'.
I don't want to use public class ControllerImplA<T> : IController<T> because the constraint is that ControllerImplA can only use DataImplA, ControllerImplB can only use DataImplB and so on so forth.
How should I correct my original code?
In my opinion, Generic class is actually a placeholder for classes. By type "T", it creates different types/classes in runtime. Which means IController<IData> and IController<DataImplA> are totally different classes, they have no inheritance relationship.

Base Class Generic Arguments Clarification [duplicate]

This question already has answers here:
What does new() mean?
(3 answers)
Closed 6 years ago.
I have an Abstract class that is implemented like this:
public abstract class BaseImplementation<T, U> : IHistory<T>
where T : class, IEntity, new()
where U : DbContext, new()
I understand that the generic argument <U> is an EF DbContext.
I understand that the generic argument <T> must be a class that implements the IEntity Interface.
What is the "new()"? Must be a new instance of a given class? What is the purpose of that?
Note that is declared in both <T> and <U>
Thank you.
From the docs:
"The new constraint specifies that any type argument in a generic class declaration must have a public parameterless constructor. To use the new constraint, the type cannot be abstract."
I don't really have much more to add to this as I think the explanation above is sufficient.
The new() is called a new constraint and it requires that the type argument has a public, parameterless constructor.
The benefit of using it is that you can create an instance of your generic type inside the class, without explicitly knowing the type passed in.
For example:
public PersonEntity : IEntity
{
// public parameterless constructor
public PersonEntity()
{
}
}
public abstract class BaseImplementation<T, U> : IHistory<T>
where T : class, IEntity, new()
where U : DbContext, new()
{
public T CreateEntity()
{
var entity = new T();
// entity will be the type passed to `T` when instantiating `BaseImplementation`
}
}
Then usage would be something like:
public class PersonImpl : BaseImplementation<PersonEntity, DataContext>
{
public void Method1()
{
var entity = CreateEntity();
// entity is typeof(PersonEntity);
}
}
new() is a constraint specifying that the type argument must have a public parameterless constructor. For more information about the generic type constraint, see MSDN。

Why can't I use generics with CreateCriteria in NHibernate?

In my Base repo I have this code which works fine:
abstract class BaseRepo <T> : IRepo <T>
{
private ISession _session;
public Entity GetById<Entity>(int Id)
{
return _session.Get<Entity>(Id);
}
// other methods
}
I want to add another method to return all rows for an object (entity). I want to do something like:
public IList<Entity> GetAll<Entity>()
{
return _session.CreateCriteria<Entity>().List<Entity>;
}
but I get an error saying:
The type 'Entity' must be a reference type in order to use it as parameter 'T' in the generic type or method 'NHibernate.ISession.CreateCriteria<T>()'
Here's my DAL design for reference: Should I use generics to simplify my DAL?
CreateCriteria method requires you to use reference types - add constraint on your DAL method:
public IList<Entity> GetAll<Entity>()
where Entity : class
{
return _session.CreateCriteria<Entity>().List<Entity>();
}
This naturally implies that any Entity type you pass to this method must be a reference type.
I also suggest naming your generic type parameter TEntity - Entity alone is a bit confusing (as it's perfectly fine name for say, entity base class).

Categories

Resources