0

Im stuck on a generics issue. I have a method that returns DbSet from a DbContext object based on the object type.

Now I want to write I method where I pass in a object that implements a interface called IBaseData (that all entities implements). I want to use the existing method to return the correct DbSet based on the type of the IBaseData object, but I can't get it to work.

I've tried various versions of , typeof and gettype.

Existing method:

public GenericRepository<T> GetRepo<T>() where T : class
{
    return new GenericRepository<T>(_context);
}

New method (this is just what Im trying to do):

public async Task SetDateLastInvoked<T>(IBaseData data, int id)
{
    var tmp = GetRepo<typeof(data)>().Where(obj => obj.Id.Equals(id));
    tmp.DateLastInvoked = DateTime.Now;

    await SaveChangesAsync();
}
SteinTheRuler
  • 3,549
  • 4
  • 35
  • 71
  • If you are using EF the problem you will run into is that EF does not support querying on interface properties, at least it didn't last I checked. This applies even if you are in a generic method – Titian Cernicova-Dragomir Dec 06 '17 at 20:03
  • Ah okei. But what if all the entities derive from a class that implements the interface. So I don't pass in the interface but the object that implements it, if you understand what I mean ? – SteinTheRuler Dec 06 '17 at 20:05
  • If you constraint the generic type on base type and not the interface I believe it will work, but I have not tested – Titian Cernicova-Dragomir Dec 06 '17 at 20:07
  • If, the type of `data` is not known at compile time, to invoke the generic method you will need reflection as described here https://stackoverflow.com/questions/232535/how-do-i-use-reflection-to-call-a-generic-method – Titian Cernicova-Dragomir Dec 06 '17 at 20:12
  • What's the origin of the `data` object when it enters `SetDateLastInvoked`? More specifically, is it attached to a context ar that point? – Gert Arnold Dec 06 '17 at 20:16
  • I asked because if it isn't you can simply attach it to the context, modify it and save it without the initial db round trip. If it *is* you should probably not do it this way. – Gert Arnold Dec 06 '17 at 21:11

2 Answers2

1

I assume you are doing something like:

public interface IBaseData 
{
  int Id { get; set; }
}

Then a method would looe like

public GenericRepository<T> GetRepo<T>() 
  where T : class
{
  return new GenericRepository<T>(_context);
}

public async Task SetDateLastInvoked<T>(int id)
  where T : class, IBaseData
{
  var tmp = GetRepo<T>().FirstOrDefault(obj => obj.Id == id));
  tmp.DateLastInvoked = DateTime.Now;

  await SaveChangesAsync();
}

Usage:

public MyClass : IBaseData 
{ 
  public Id { get; set }
}

SetDateLastInvoked<MyClass>(1);
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • @TheRuler won't hurt to also restrict your GetRepo to `where T: IBaseData` - will prevent passing some type completely unrelated to your entities (which will fail at runtime). – Evk Dec 06 '17 at 20:17
  • I think I've done that now.. not exactly what you said, but `where T : class, IBaseData` – SteinTheRuler Dec 06 '17 at 20:33
0

Ended up with this, based on previous answer:

public async Task SetLastInvoked<T>(int id) where T : class, IBaseData
{
    var tmp = await GetRepo<T>().Single(obj => obj.Id.Equals(id));
    tmp.DateLastInvoked = DateTime.Now;

    await SaveChangesAsync();
}

I also created another method based on the same pattern:

public async Task DeleteItem<T>(int id, bool removeRow) where T : class, IBaseData
{
    if (removeRow)
    {
        GetRepo<T>().Delete(await GetRepo<T>().Single(obj => obj.Id.Equals(id)));
    }
    else
    {
        var tmp = await GetRepo<T>().Single(obj => obj.Id.Equals(id));
        tmp.Active = false;
    }

    await SaveChangesAsync();
}
SteinTheRuler
  • 3,549
  • 4
  • 35
  • 71