Created
December 29, 2018 16:41
-
-
Save sm-g/07e0ef732623c5ad8510e575b0b2ec2f to your computer and use it in GitHub Desktop.
EF Core InMemory DB foreign keys checker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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