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);
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With