Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

T must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TModel' in the generic type or method

I've tried searching SO for the answer and stumbled upon similar problems but I couldn't manage to use them for solving my problem, so please try not to mark this as a duplicate. Let's move on to the real deal:

I have a generic library for standardizing entity framework database first models. These are the generic classes that I've created:

public abstract class GenericLookupModel : IActive, ICreated, IModified, IIdentity, IStringValue
{
    public bool is_active { get; set; }
    public string value { get; set; }
    public string description { get; set; }
    public DateTime created_on { get; set; }
    public string created_by { get; set; }
    public DateTime modified_on { get; set; }
    public string modified_by { get; set; }
    public int id {get;set;}

    public void SetCreated(string creator = "SYSTEM")
    {
        created_by = creator;
        created_on = DateTime.Now;
    }

    public void SetModified(string modifier = "SYSTEM")
    {
        modified_by = modifier;
        modified_on = DateTime.Now;
    }
}

And an class for the ViewModel with pre-set MVC attributes

public abstract class GenericLookupViewModel
{
    [Key]
    public int ID { get; set; }

    [Required]
    [StringLength(300)]
    public string Name { get; set; }

    [StringLength(4000)]
    public string Description { get; set; }

    [Required]
    public bool Active { get; set; }

    [StringLength(50)]
    [DisplayName("Record last modified by")]
    public string ModifiedBy { get; set; }

    [DisplayName("Record last modified Date")]
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
    public DateTime ModifiedOn { get; set; }

    [StringLength(50)]
    [DisplayName("Record created by")]
    public string CreatedBy { get; set; }

    [DisplayName("Record creation Date")]
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
    public DateTime CreatedOn { get; set; }
}

Also, I've created a service class that I intend to use inside the controller for getting the data:

public abstract class GenericLookupModelDataService<TModel, TViewModel> : object 
    where TModel : GenericLookupModel, new()
    where TViewModel : GenericLookupViewModel, new()
{
    private readonly DbContext _db;

    private DbContext entities
    {
        get { return _db; }
    }

    public GenericLookupModelDataService()
    {
        _db =
            new DbContext(
                System.Configuration.ConfigurationManager.ConnectionStrings["DefaultConnectionString"].ConnectionString);
    }

    public virtual IEnumerable<TViewModel> ReadAllActive()
    {
        return entities.Set<TModel>().Where(x => x.is_active).Select(product => new TViewModel
        {
            ID = product.id,
            Active = product.is_active,
            Description = product.description,
            Name = product.value,
            CreatedBy = product.created_by,
            CreatedOn = product.created_on,
            ModifiedBy = product.modified_by,
            ModifiedOn = product.modified_on
        });
    }

    public virtual IEnumerable<TViewModel> Read()
    {
        return entities.Set<TModel>().Select(product => new TViewModel
        {
            ID = product.id,
            Active = product.is_active,
            Description = product.description,
            Name = product.value,
            CreatedBy = product.created_by,
            CreatedOn = product.created_on,
            ModifiedBy = product.modified_by,
            ModifiedOn = product.modified_on
        });
    }

    public virtual void Create(TViewModel product, string username = "SYSTEM")
    {
        var entity = new TModel
        {
            is_active = product.Active,
            description = product.Description,
            value = product.Name,
        };

        entity.SetCreated();
        entity.SetModified();

        _db.Set<TModel>().Add(entity);
        _db.SaveChanges();
    }

    public virtual void Update(TViewModel product, string username = "SYSTEM")
    {
        var entity = new TModel
        {
            id = product.ID,
            is_active = product.Active,
            description = product.Description,
            value = product.Name
        };
        entity.SetModified();


        _db.Set<TModel>().Attach(entity);
        entities.Entry(entity).State = EntityState.Modified;
        entities.SaveChanges();
    }

    public virtual void Destroy(TViewModel product)
    {
        var entity = new TModel {id = product.ID};

        entities.Set<TModel>().Attach(entity);
        entities.Set<TModel>().Remove(entity);
        entities.SaveChanges();
    }

    public virtual TViewModel GetByID(int ID)
    {
        var item = entities.Set<TModel>().Find(ID);
        var result = new TViewModel
        {
            ID = item.id,
            Active = item.is_active,
            CreatedBy = item.created_by,
            CreatedOn = item.created_on,
            Description = item.description,
            ModifiedBy = item.modified_by,
            ModifiedOn = item.modified_on,
            Name = item.value
        };
        return result;
    }

    public void Dispose()
    {
        entities.Dispose();
    }

}

The library compiles fine, I use it inside the data layer project inside my MVC App. Start off by creating a new view model:

public class RoleViewModel : GenericLookupViewModel
{


}

Then, lets create a service:

public class RoleService : GenericLookupModelDataService<tblkp_Role, RoleViewModel> 
{

}

Make the Entity Framework class inherit from the abstract model:

partial class tblkp_Role : GenericLookupModel
{

}

Finally let's create our controller:

public class EmployeeController : Controller
{
    private RoleService roleService;

    public EmployeeController()
    {
        dataService = new EmployeeService();
        PopulateLookups();
    }

    private void PopulateLookups()
    {
        roleService = new RoleService();
        ViewData["roles"] = roleService.ReadAllActive();
    }

    public ActionResult Index()
    {
        return View();
    }

}

Sorry for the wall-of-code, some code has already been removed for brevity. While compiling it gives me 3 errors: enter image description here

UPDATE: Provided tblk_Role class generated automatically by EF (DB First approach):

using System;
using System.Collections.Generic;

public partial class tblkp_Role
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public tblkp_Role()
    {
        this.tbl_Employee = new HashSet<tbl_Employee>();
    }

    public int id { get; set; }
    public string value { get; set; }
    public string desciption { get; set; }
    public bool is_active { get; set; }
    public System.DateTime created_on { get; set; }
    public string created_by { get; set; }
    public System.DateTime modified_on { get; set; }
    public string modified_by { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<tbl_Employee> tbl_Employee { get; set; }
}

UPDATE 2: Erros in plain text format:

Error 33 'DataLayer.Model.tblkp_Role' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TModel' in the generic type or method 'MyLib.Model.GenericLookupModelDataService<TModel,TViewModel>' c:\Projects\Sources\MyLib\bin\Release\MyLib.dll

Error 32 The type 'DataLayer.Model.tblkp_Role' cannot be used as type parameter 'TModel' in the generic type or method 'MyLib.Model.GenericLookupModelDataService<TModel,TViewModel>'. There is no boxing conversion from 'DataLayer.Model.tblkp_Role' to 'MyLib.Model.GenericLookupModel'. c:\Projects\Sources\MyLib\bin\Release\MyLib.dll

like image 668
Adrian K. Avatar asked Jan 20 '16 14:01

Adrian K.


2 Answers

You have the following:

public abstract class GenericLookupModelDataService<TModel, TViewModel> : object 
    where TModel : GenericLookupModel, new()
    where TViewModel : GenericLookupViewModel, new()
{
  // ...

That class has two generic paramters called TModel and TViewModel. Each of these has constraints to it, indicated after the where contextual keyword.

For TModel the constraints are:

  • a base class constraint requiring that the class GenericLookupModel must be a base class of what ever type is substituted in for TModel, and
  • a constructor constraint new() requiring that the type used for TModel must expose a public instance constructor that takes zero arguments.

One of the errors you ask about is:

Error 33 'DataLayer.Model.tblkp_Role' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TModel' in the generic type or method 'MyLib.Model.GenericLookupModelDataService<TModel,TViewModel>'

This simply means that the type tblkp_Role which you try to use for TModel does not conform to the constructor constraint. Do you have the 0-parameter constructor?

Another error you ask about is:

Error 32 The type 'DataLayer.Model.tblkp_Role' cannot be used as type parameter 'TModel' in the generic type or method 'MyLib.Model.GenericLookupModelDataService<TModel,TViewModel>'. There is no boxing conversion from 'DataLayer.Model.tblkp_Role' to 'MyLib.Model.GenericLookupModel'.

This indicates that the base class constraint is not met. Since the error text talks about "boxing conversion", it appears that the type tblkp_Role which the compiler is using, is in fact a value type (struct type, or enum type). Types like that can never derive from GenericLookupModel as the constraint requires.

It must be that the type tblkp_Role that the C# compiler uses, is another type than the type you define with partial class tblkp_Role : GenericLookupModel. You may have some conflicting names or some duplicate code/names from referenced projects.

In the image version of your compile-time errors we see that the compiler also complains that the type tblkp_Role which you use is declared in an assembly which you have no reference to. Try fixing that one first. Maybe the other ones will go away once the compiler can see all details of tblkp_Role because it has the reference to the project that defines that type.

like image 159
Jeppe Stig Nielsen Avatar answered Nov 10 '22 07:11

Jeppe Stig Nielsen


The error you mentioned is usually faced when you try to use the same generic type parameter in different classes without defining all the constraints in at least one of them. See this Jon Skeet's answer for clarity.

But you are using TModel in only one class here i.e. GenericLookupModelDataService, therefore I tried the following:

I wrote all your code in the same code file that means no external library. Something like this:

class Program
{
    static void Main(string[] args)
    {
        RoleService roleService = new RoleService();
    }
}

class RoleService : GenericLookupModelDataService<tblkp_Role, RoleViewModel> 
{ }

public abstract class GenericLookupModelDataService<TModel, TViewModel> : object
    where TModel : GenericLookupModel, new()
    where TViewModel : GenericLookupViewModel, new()
{ }

public abstract class GenericLookupViewModel { }

public abstract class GenericLookupModel { }

public class RoleViewModel : GenericLookupViewModel { }

public partial class tblkp_Role : GenericLookupModel 
{
}

public partial class tblkp_Role
{
    public tblkp_Role()
    {

    }
}

This compiles successfully. Therefore I suspect that compiler is unaware of the full definition of tblkp_Role.

I would suggest re building the library and re-referencing it again (Also check the reference path to ensure you are not mistakenly referencing older version).

I have faced similar issues with partial classes which are automatically created by EF in DB first approach specifically when I tried to define metadata classes.

like image 35
Rachit Pandey Avatar answered Nov 10 '22 06:11

Rachit Pandey