public ReflectionTable(IRelationalDatabase database, IDatabaseDialect dialect, Type tableType) { Database = database ?? throw new ArgumentNullException(nameof(database)); Dialect = dialect ?? throw new ArgumentNullException(nameof(dialect)); InstanceType = tableType ?? throw new ArgumentNullException(nameof(tableType)); Name = Dialect.GetQualifiedNameOrDefault(database, InstanceType); TypeProvider = new ReflectionTableTypeProvider(Dialect, InstanceType); _columns = new Lazy <IReadOnlyList <IDatabaseColumn> >(LoadColumnList); _checkLookup = new Lazy <IReadOnlyDictionary <Identifier, IDatabaseCheckConstraint> >(LoadChecks); _uniqueKeyLookup = new Lazy <IReadOnlyDictionary <Identifier, IDatabaseKey> >(LoadUniqueKeys); _indexLookup = new Lazy <IReadOnlyDictionary <Identifier, IDatabaseIndex> >(LoadIndexes); _parentKeyLookup = new Lazy <IReadOnlyDictionary <Identifier, IDatabaseRelationalKey> >(LoadParentKeys); _childKeys = new Lazy <IReadOnlyCollection <IDatabaseRelationalKey> >(LoadChildKeys); _primaryKey = new Lazy <Option <IDatabaseKey> >(LoadPrimaryKey); }
// TODO: see if it's possible to create a foreign key to a synonym in sql server // this should be possible in oracle but not sure private IReadOnlyDictionary <Identifier, IDatabaseRelationalKey> LoadParentKeys() { var result = new Dictionary <Identifier, IDatabaseRelationalKey>(); var parentKeys = TypeProvider.ParentKeys; if (parentKeys.Empty()) { return(result); } foreach (var declaredParentKey in parentKeys) { var fkColumns = declaredParentKey.Columns.Select(GetColumn).ToList(); var parentName = Dialect.GetQualifiedNameOrDefault(Database, declaredParentKey.TargetType); var parentOption = Database.GetTable(parentName); if (parentOption.IsNone.ConfigureAwait(false).GetAwaiter().GetResult()) { throw new Exception("Could not find parent table with name: " + parentName.ToString()); } var tmpOption = parentOption.ToOption().ConfigureAwait(false).GetAwaiter().GetResult(); var parent = tmpOption.MatchUnsafe(t => t, () => null !); var parentTypeProvider = new ReflectionTableTypeProvider(Dialect, declaredParentKey.TargetType); var parentInstance = parentTypeProvider.TableInstance; var keyObject = declaredParentKey.KeySelector(parentInstance); var parentKeyName = Dialect.GetAliasOrDefault(keyObject.Property !); IDatabaseKey parentKey; if (keyObject.KeyType == DatabaseKeyType.Primary) { // TODO: provide better error messaging, maybe to KeyNotFoundException too? parentKey = parent.PrimaryKey .Match(pk => pk, () => throw new Exception("Could not find matching parent key for foreign key.")); } else if (keyObject.KeyType == DatabaseKeyType.Unique) { var uniqueKey = parent.UniqueKeys.FirstOrDefault(uk => uk.Name.Where(ukName => ukName.LocalName == parentKeyName).IsSome ); parentKey = uniqueKey ?? throw new Exception("Could not find matching parent key for foreign key."); } else { throw new Exception("Cannot point a foreign key to a key that is not the primary key or a unique key."); // TODO: improve error messaging } // check that columns match up with parent key -- otherwise will fail // TODO: don't assume that the FK is to a table -- could be to a synonym // maybe change interface of Synonym<T> to be something like Synonym<Table<T>> or Synonym<Synonym<T>> -- could unwrap at runtime? var childKeyName = Dialect.GetAliasOrDefault(declaredParentKey.Property !); var childKey = new ReflectionForeignKey(childKeyName, parentKey, fkColumns); var deleteAttr = Dialect.GetDialectAttribute <OnDeleteActionAttribute>(declaredParentKey.Property !); var deleteAction = deleteAttr?.Action ?? ReferentialAction.NoAction; var updateAttr = Dialect.GetDialectAttribute <OnUpdateActionAttribute>(declaredParentKey.Property !); var updateAction = updateAttr?.Action ?? ReferentialAction.NoAction; childKey.Name.IfSome(name => result[name.LocalName] = new ReflectionRelationalKey(Name, childKey, parent.Name, parentKey, deleteAction, updateAction)); } return(result); }