Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Querying against DbContext.Set(TypeVariable) in Entity Framework

I'm re-factoring an application to invert some dependencies. Part of the original code uses DbContext.Set<MyEntityType>() to obtain the set to query against.

With the inversion, the MyEntityType is no longer explicitly known to the code using DbContext.Set<MyEntityType>(), so now I'm using DbContext.Set(TypeVariable) instead.

The re-factoring works to the extent that the correct DbSet is being returned.

However, the type DbContext.Set(TypeVariable).Local is IList (contained type unknown) where-as the type of DbContext.Set<MyEntityType>().Local was ObservableCollection<MyEntityType>. This means that it's now impossible to do Linq against the DbSet.

The best workaround I've been able to achieve is to cast to an interface that MyEntityType and other entity types implement (some code omitted for clarity)

var set = Context.Set(targetType);
var entity = set.Local.OfType<IActionTarget>()
    .FirstOrDefault(l => l.Id == key.Id && l.EffectiveDate == key.EffectiveDate);
var querables = set as IQueryable<IActionTarget>;
entity = querables.FirstOrDefault(e => e.Id == key.Id && e.EffectiveDate == key.EffectiveDate);

So, two questions:

Why doesn't DbContext.Set(TypeVariable) return a strongly typed set?

Is there a better way to do the dependency inversion?

Some further details as requested

It's all about dependencies. The Model contains POCO classes which are persisted via EF Code First in the typical way (but via a Repository). An ActionEvaluator takes some incoming data, and via Repository methods, determines what actions need to occur - hence the queries against the DbSets.

In the original code, there was only one type of incoming data (CSV of a particular format) and the ActionEvaluator had a tight dependency to this data and knew which POCO classes were applicable to which CSV records.

Now, we want to expand to use different CSV formats and web api messages. To do this, we need to invert the dependencies so that the DataSource tells the ActionEvaluator what POCO classes it's records apply to. This is done by way of a Type variable.

So, the ActionEvaluator can no longer use a generic type parameter, but it can pass the type variable to the Repository which uses it to find the correct DbSet.

The problem is the difference between the two ways of finding the DbSet - DbContext.Set<AnEntity>() and DbContext.Set(TypeVariable).

I guess I'm asking for an enhancement in EF to make these two functionally equivalent in their return values, but that may not be possible since the types of the second version are determined at runtime.

Here's the full method code as requested:

    private IActionTarget DbContextGetEntity(Type targetType, IActionTarget key)
    {
        var set = Context.Set(targetType);
        if (set == null)
        {
            throw new Exception("Unable to find DbSet for type '{0}'".F(targetType.Name));
        }

        // Look in the local cache first
        var entity = set.Local.OfType<IActionTarget>()
            .FirstOrDefault(l => l.Id == key.Id && l.EffectiveDate == key.EffectiveDate);

        if (entity == null)
        {
            // If not found locally, hit the database
            var querables = set as IQueryable<IActionTarget>;
            if (querables != null)
            {
                entity = querables.FirstOrDefault(e => e.Id == key.Id && e.EffectiveDate == key.EffectiveDate);
            }
        }
        return entity;
    }

Ideally, I want to replace

var entity = set.Local.OfType<IActionTarget>()
    .FirstOrDefault(l => l.Id == key.Id && l.EffectiveDate == key.EffectiveDate);

with

var entity = set.Local.OfType(targetType)
    .FirstOrDefault(l => l.Id == key.Id && l.EffectiveDate == key.EffectiveDate);
like image 330
Ackroydd Avatar asked Oct 22 '22 14:10

Ackroydd


1 Answers

I haven't compiled it so please excuse any formatting issues - can you use the dynamic type to achieve the same thing?

private IActionTarget DbContextGetEntity(Type targetType, IActionTarget key)
{
    dynamic instance = Activator.CreateInstance(targetType);
    return DbContextGetEntity(instance, key);
}

private IActionTarget DbContextGetEntity<T>(T instance, IActionTarget key)
    where T : class, IActionTarget
{
    var set = Context.Set<T>(targetType);
    if (set == null)
    {
        throw new Exception();
    }

    // Look in the local cache first
    var entity = set.Local
        .FirstOrDefault(l => l.Id == key.Id && l.EffectiveDate == key.EffectiveDate);

    if (entity == null)
    {
        // If not found locally, hit the database
        entity = set
            .FirstOrDefault(e => e.Id == key.Id && e.EffectiveDate == key.EffectiveDate);
    }
    return entity;
}
like image 108
qujck Avatar answered Oct 24 '22 11:10

qujck