Search code examples
c#linqgenerics

How to use Linq First with generic <T> base class


I am working through this Xaml Brewer article and altering it slightly to use my models. It uses a generic MasterDetailViewModel base class of type T.

The base class is declared thus

    public abstract partial class MasterDetailViewModel<T> : ObservableObject
    {
        private readonly ObservableCollection<T> items = new();
etc...

My derived class declares as .

    public partial class HomePageViewModel : MasterDetailViewModel<Tenant>
    {

etc..

Class Tenant is

[Table("tenants")]
public partial class Tenant
{
    //[Column("email")]
    public string Email { get; set; }

    //[Column("first_name")]
    public string FirstName { get; set; }

    [Key]
    public int Id { get; set; }
etc..

My derived class contains a RelayCommand and a method

    [RelayCommand]
        private void Delete(int param) //param is Id of selected list item
    {
        DeleteCommand_Executed(param);
    }

        private void DeleteCommand_Executed(int parm)
        {
            if (parm > 0)
            {
                var toBeDeleted = Items.First(c => c.Id == parm); //get the record with this Id
                DeleteItem(toBeDeleted); //delete from the database
            }
        }

I want to move the RelayCommand and DeleteCommand_Executed method up into the base class. If I just move the relay command into the base class and add this protected abstract void DeleteCommand_Executed(int parm); then with this in the derived class protected override void DeleteCommand_Executed(int parm) etc. it all works fine but if I move DeleteCommand_Executed into the base class I get this error

Error (active) 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 (are you missing a using directive or an assembly reference?)

I have tried adding a type identifier var toBeDeleted = Items.First<T>(c => c.Id == parm); I have looked at a few similar questions on SO and a couple of googled items and I think what I'm being told is that the T is not known until the expression has run so it cant know what properties T has? So is it possible to achieve my aim of putting everything into the base class?

Thanks in advance


Solution

  • First, define an interface like below:

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

    Then, redefine MasterDetailViewModel and Tenant classes like this:

    public abstract partial class MasterDetailViewModel<T> : ObservableObject where T: IHasId
    
    public partial class Tenant : IHasId
    

    Now, moving the DeleteCommand_Executed method to the MasterDetailViewModel class should work fine.