Skip to content

Instantly share code, notes, and snippets.

@sm-g
Created December 29, 2018 16:41
Show Gist options
  • Save sm-g/07e0ef732623c5ad8510e575b0b2ec2f to your computer and use it in GitHub Desktop.
Save sm-g/07e0ef732623c5ad8510e575b0b2ec2f to your computer and use it in GitHub Desktop.
EF Core InMemory DB foreign keys checker
public static class DbForeignKeysChecker
{
private static readonly Dictionary<DbContext, Dictionary<Type, Keys>> KeysPerEntityTypeOfContext = new Dictionary<DbContext, Dictionary<Type, Keys>>();
public static void SaveChangesWithCheck(this DbContext context)
{
context.SaveChanges();
context.CheckForeignKeys();
}
private static void CheckForeignKeys(this DbContext db)
{
if (!KeysPerEntityTypeOfContext.ContainsKey(db))
{
KeysPerEntityTypeOfContext[db] = db.Model
.GetEntityTypes()
.Where(x => !x.IsOwned())
.ToDictionary(
x => x.ClrType,
x => new Keys
{
PrimaryKeyProperties = x.FindPrimaryKey().Properties.Select(p => p.PropertyInfo).ToList(),
ForeignKeys = x.GetForeignKeys()
.Where(z => !z.IsOwnership)
.Select(z => new ForeignKey
{
PrincipalType = z.PrincipalEntityType.ClrType,
PrincipalPropName = z.DependentToPrincipal.Name,
Properties = z.Properties.Select(p => p.PropertyInfo ?? throw new Exception($"No FK property info (from {x.ClrType} to {z.PrincipalEntityType.ClrType})")).ToList()
})
.ToList()
}
);
}
var dbSets = db.GetType().GetProperties()
.Where(x => x.PropertyType.IsGenericType && x.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
.Select(x => new Set
{
EntityType = x.PropertyType.GetGenericArguments()[0],
List = x.GetValue(db) as IEnumerable<dynamic>
})
.Where(x => x.List != null)
.ToList();
var keysPerEntityType = KeysPerEntityTypeOfContext[db];
foreach (var dbSet in dbSets)
{
var foreignKeys = keysPerEntityType[dbSet.EntityType].ForeignKeys;
foreach (var fk in foreignKeys)
{
var dbSetOfPrincipal = dbSets.FirstOrDefault(x => x.EntityType == fk.PrincipalType);
if (dbSetOfPrincipal == null)
{
throw new Exception($"FK validation failed: DbSet<{fk.PrincipalType.Name}> is null");
}
var principalPrimaryKeysProps = keysPerEntityType[fk.PrincipalType].PrimaryKeyProperties;
foreach (var entity in dbSet.List)
{
var fkPropsValues = fk.Properties.Select(x => x.GetValue(entity)).ToList();
if (fkPropsValues.All(x => x != null) &&
dbSetOfPrincipal.List.FirstOrDefault(x => principalPrimaryKeysProps.Select(z => z.GetValue(x)).SequenceEqual(fkPropsValues)) == null)
{
throw new Exception($"FK validation failed: checking DbSet<{dbSet.EntityType.Name}>, principal {fk.PrincipalPropName}({fk.PrincipalType.Name}) with key '{fkPropsValues.ToSeparatedString()}' not found");
}
}
}
}
}
private class Keys
{
public List<PropertyInfo> PrimaryKeyProperties { get; set; }
public List<ForeignKey> ForeignKeys { get; set; }
}
private class ForeignKey
{
public Type PrincipalType { get; set; }
public string PrincipalPropName { get; set; }
public List<PropertyInfo> Properties { get; set; }
}
private class Set
{
public Type EntityType { get; set; }
public IEnumerable<dynamic> List { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment