0

I'm implementing generics for Add, Remove, and Find, because I'm implementing multi-tenancy that gives me access to IQueryables from the database, as opposed to DbSets.

How Add was implemented:

Code before:

interest = db.Interests.Add(new Interest()) // Interests is dbSet

Code after:

interest = db.Add(new Interest()) // Interests is dbSet

// in db class
public T Add<T>(T item) where T : class, ITenantData
    {
        var set = _appContext.Set<T>();
            return set.Add(item);
    }

This works. Now I'm trying to do the same thing for Find, but because I'm working with an Id and not a Typed parameter, I'm a bit stuck.

Code before:

var aptitude = db.Aptitudes.Find(interest.AptitudeId);  // Aptitudes is a DbSet

Code after:

var aptitude = db.Find(interest.AptitudeId); // how to identify type?

I can't do this:

var aptitude = db.Aptitudes.Find(interest.AptitudeId);

Because Aptitudes is now an IQueryable.

So this is currently failing:

public T Find<T>(T query) where T : class, ITenantData
    {
        var set = _mentorContext.Set<T>();
        return set.Find(query);
    }

Because the type isn't identified - I get :

The type 'int' must be a reference type in order to use it as parameter 'T' in the generic type or method 'App.Context.TenantContext.Find(T)' Service.cs

I'm a bit new to generics, any help here would be much appreciated.

SB2055
  • 10,654
  • 29
  • 87
  • 185
  • 1
    Why can't you do `db.Aptitudes.Find(interest.AptitudeId);`? That seems like the best option by far. Why does it being an `IQueryable` mean you can't use it? – Servy Nov 04 '13 at 18:12
  • @Servy - because Aptitudes is now an IQueryable - per this post: http://stackoverflow.com/questions/19758080/dbset-modelbuilder-and-ef-navigation-properties/19759042#comment29364347_19759042 – SB2055 Nov 04 '13 at 18:15
  • 1
    Still not seeing the problem. Calling `Find` doesn't change the type of `Aptitudes`, nor would it being an `IQueryable` mean you couldn't find that item using the methods available through LINQ. Still not seeing a problem here. – Servy Nov 04 '13 at 18:17
  • Ah - I'm trying to use Find() over Where() for performance reasons. – SB2055 Nov 04 '13 at 18:19
  • So then change the type of the method to accept a set, rather than an IQueryable, still not seeing a problem here. – Servy Nov 04 '13 at 18:19
  • `public T Find(params object[] keyComponents) where T : class, ITenantData` -- note that `.Find` only works for the primary key. For candidate keys or other filters, you need `.Where` or some other LINQ extension method. – danludwig Nov 04 '13 at 18:27

1 Answers1

1

Solve this with extension methods.

public IQueryable<T> Query<T>() where T : class, ITenantData
{
    return _mentorContext.Set<T>().AsNoTracking();
}

...meanwhile in another class...

public static MentorContextExtensions
{
    public static InterestEntity ByAptitudeId(
        this IQueryable<InterestEntity> queryable, 
        int aptitudeId)
    {
        return queryable.SingleOrDefault(x => x.AptitudeId == aptitudeId);
    }
}

...usage against IQueryable<T>...

db.Query<InterestEntity>().ByAptitudeId(interest.AptitudeId);

The example above returns a SingleOrDefault, but you could just as easily return sets (pre-filtered IQueryables or IEnumerables) using .Where, bools using .Any, etc.

To maintain usage of the .Find (by primary key) method on the IDbSet, you can do this:

public T Find<T>(params object[] keyComponents) where T : class, ITenantData
{
    return _mentorContext.Set<T>().Find(keyComponents);
}

...and use it like this...

var aptitude = db.Find<Aptitude>(interest.AptitudeId);

... that said, using .Find may only be faster when you are doing inserts, updates, deletes with the entity, in some kind of mutation code. .Find will always keep the entity attached to the context, which is what you want when you need to mutate the data. However you cannot invoke .AsNoTracking() on it because it returns a single entity. If you only need the entity to read its properties or for display purposes, .AsNoTracking will keep the entire entity graph (the entity and its navigation / collection properties) detached from the context, which may be faster than using .Find. If you later want to use the same entity instance in mutation code, you can attach it to the context at that time.

danludwig
  • 45,241
  • 21
  • 150
  • 230