Home > OS >  C# How to access member of generic type in a generic class?
C# How to access member of generic type in a generic class?

Time:06-03

    public class DS2DbContext : DbContext
    {
        public DbSet<DocumentFileData> DocumentFileData { get; set; }
        public DbSet<WatermarkFileData> WatermarkFileData { get; set; }
        public DS2DbContext(DbContextOptions<DS2DbContext> options) 
            : base(options) { }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        }
    }

    public class FileDataController<T> : ODataController where T : class
    {
        private readonly DS2DbContext _context;
        private readonly ILogger<FileDataController<T>> _logger;

        public FileDataController(DS2DbContext dbContext,
                                  ILogger<FileDataController<T>> logger)
        {
            _logger = logger;
            _context = dbContext;
        }

        [EnableQuery]
        public SingleResult<T> Get([FromODataUri] Guid ID)
        {
            var result = _context.Set<T>().Where(i => i.ID == ID);
            // Error CS1061: 'T' does not contain a definition for 'ID'
            // and no accessible extension method 'ID' accepting a
            // first argument of type 'T' could be found
            return SingleResult.Create(result);
        }
    }

How do I properly (and if possible, elegantly) access members of variables with type T and get their values?

Update

When I use an interface like this:

    public class FileDataController<T> : ODataController where T : IFileData
    {
        private readonly DS2DbContext _context;
        private readonly ILogger<FileDataController<T>> _logger;

        public FileDataController(DS2DbContext dbContext,
                                  ILogger<FileDataController<T>> logger)
        {
            _logger = logger;
            _context = dbContext;
        }

        [EnableQuery]
        public SingleResult<T> Get([FromODataUri] Guid ID)
        {
            var result = _context.Set<T>().Where(i => i.ID == ID);
            // Error CS0452: The type 'T' must be a reference type in
            // order to use it as parameter 'TEntity' in the generic
            // type or method 'DbSet<TEntity>'            
            return SingleResult.Create(result);
        }
    }

I am getting an error when calling _context.Set().

What I have come up with is

    var result = _context.Set<T>()
        .Where(x => (Guid) x.GetType().GetProperty("ID").GetValue(x) == ID);

But this looks horribly complicated.

The story behind this

is that I have to store data of the exact same structure in two different database table depending on the data's semantics (requirements document or watermark). Since I am doing Blazor, code first, I need to have two different classes for this to make it correctly create two tables and handle them via two different controllers. However, these controllers share the exact same code and the exact same underlying data structure. Here is a simplified example just implementing an ID:

    public interface IFileData
    {
        public Guid ID { get; set; }
    }

    using System.ComponentModel.DataAnnotations;
    public class FileData : IFileData
    {
        [Key]
        public Guid ID { get; set; } = Guid.Empty;
    }

    public class DocumentFileData : FileData { }

    public class WatermarkFileData : FileData { }

Consequently, there is a controller for DocumentFileData and one for WatermarkFileData to access the proper database table for each type of data. However, all underlying operations are identical. So I hope to able to solve this via a generic class to save me the trouble of having to make identical changes to two different classes everytime something in file data handling changes.

CodePudding user response:

Since you have an common interface exposing ID property provide corresponding generic constraint - (you need to specify both generic type constraints - interface and class):

public class FileDataController<T> : ODataController where T : class, IFileData

CodePudding user response:

As @GuruSiton correctly commented, you should introduce an interface with ID in it:

interface IClassWithId
{
    Guid ID { get; } // Notice that you only require get - the implementer can choose how the ID get init'd - private / c'tor / etc
}

public class FileDataController<T> : ODataController where T : IClassWithId
{ ... }

This assumes that you can make - or require - all classes used as T to implement IClassWithId. If you're stuck with existing classes that define their own ID and can't change them, then you must resort to reflection, as hinted to by @NaeemAhmed.

  • Related