Given this extremely simple model:
public class MyContext : BaseContext
{
public DbSet Foos { get; set; }
public DbSet Bars { g
If anyone wants a general approach to solve this problem, here you have a custom DbContext which finds out properties based on these constraints:
virtualValidationAttribute attribute.After retrieving this list, on any SaveChanges in which have something to modify it will load all references and collections automatically avoiding any unexpected exception.
public abstract class ExtendedDbContext : DbContext
{
public ExtendedDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
public ExtendedDbContext(DbConnection existingConnection, bool contextOwnsConnection)
: base(existingConnection, contextOwnsConnection)
{
}
public ExtendedDbContext(ObjectContext objectContext, bool dbContextOwnsObjectContext)
: base(objectContext, dbContextOwnsObjectContext)
{
}
public ExtendedDbContext(string nameOrConnectionString, DbCompiledModel model)
: base(nameOrConnectionString, model)
{
}
public ExtendedDbContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection)
: base(existingConnection, model, contextOwnsConnection)
{
}
#region Validation + Lazy Loading Hack
///
/// Enumerator which identifies lazy loading types.
///
private enum LazyEnum
{
COLLECTION,
REFERENCE,
PROPERTY,
COMPLEX_PROPERTY
}
///
/// Defines a lazy load property
///
private class LazyProperty
{
public string Name { get; private set; }
public LazyEnum Type { get; private set; }
public LazyProperty(string name, LazyEnum type)
{
this.Name = name;
this.Type = type;
}
}
///
/// Concurrenct dictinary which acts as a Cache.
///
private ConcurrentDictionary> lazyPropertiesByType =
new ConcurrentDictionary>();
///
/// Obtiene por la caché y si no lo tuviese lo calcula, cachea y obtiene.
///
private IList GetLazyProperties(Type entityType)
{
return
lazyPropertiesByType.GetOrAdd(
entityType,
innerEntityType =>
{
if (this.Configuration.LazyLoadingEnabled == false)
return new List();
return
innerEntityType
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(pi => pi.CanRead)
.Where(pi => !(pi.GetIndexParameters().Length > 0))
.Where(pi => pi.GetGetMethod().IsVirtual)
.Where(pi => pi.GetCustomAttributes().Exists(attr => typeof(ValidationAttribute).IsAssignableFrom(attr.GetType())))
.Select(
pi =>
{
Type propertyType = pi.PropertyType;
if (propertyType.HasGenericInterface(typeof(ICollection<>)))
return new LazyProperty(pi.Name, LazyEnum.COLLECTION);
else if (propertyType.HasGenericInterface(typeof(IEntity<>)))
return new LazyProperty(pi.Name, LazyEnum.REFERENCE);
else
return new LazyProperty(pi.Name, LazyEnum.PROPERTY);
}
)
.ToList();
}
);
}
#endregion
#region DbContext
public override int SaveChanges()
{
// Get all Modified entities
var changedEntries =
this
.ChangeTracker
.Entries()
.Where(p => p.State == EntityState.Modified);
foreach (var entry in changedEntries)
{
foreach (LazyProperty lazyProperty in GetLazyProperties(ObjectContext.GetObjectType(entry.Entity.GetType())))
{
switch (lazyProperty.Type)
{
case LazyEnum.REFERENCE:
entry.Reference(lazyProperty.Name).Load();
break;
case LazyEnum.COLLECTION:
entry.Collection(lazyProperty.Name).Load();
break;
}
}
}
return base.SaveChanges();
}
#endregion
}
Where IEntity is:
public interface IEntity
{
T Id { get; set; }
}
These extensions were used in this code:
public static bool HasGenericInterface(this Type input, Type genericType)
{
return
input
.GetInterfaces()
.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType);
}
public static bool Exists(this IEnumerable source, Predicate predicate)
{
foreach (T item in source)
{
if (predicate(item))
return true;
}
return false;
}
Hope it helps,