private void CreateAssociationType(LoadMethodSessionState session, List<RelationshipDetailsRow> columns) { Debug.Assert(columns.Count != 0, "should have at least one column"); RelationshipDetailsRow firstRow = columns[0]; // get the entity types for the ends EntityType pkEntityType; EntityType fkEntityType; if (!TryGetEndEntities(session, firstRow, out pkEntityType, out fkEntityType)) { return; } if (!AreRelationshipColumnsTheTypesEntireKey(pkEntityType, columns, r => r.PKColumn)) { session.AddErrorsForType(pkEntityType, new EdmSchemaError(Strings.UnsupportedDbRelationship(firstRow.RelationshipName), (int)ModelBuilderErrorCode.UnsupportedDbRelationship, EdmSchemaErrorSeverity.Warning)); return; } UniqueIdentifierService usedEndNames = new UniqueIdentifierService(false); // figure out the lower bound of the pk end bool someFkColmnsAreNullable; if (_targetEntityFrameworkVersion == EntityFrameworkVersions.Version1) { someFkColmnsAreNullable = AreAllFkKeyColumnsNullable(fkEntityType, columns); } else { someFkColmnsAreNullable = AreAnyFkKeyColumnsNullable(fkEntityType, columns); } RelationshipMultiplicity pkMultiplicity = someFkColmnsAreNullable ? RelationshipMultiplicity.ZeroOrOne : RelationshipMultiplicity.One; //Get the Delete Action for the end and set it. //The only DeleteAction we support is Cascade, ignor all others for now. OperationAction onDeleteAction = OperationAction.None; if (firstRow.RelationshipIsCascadeDelete) { onDeleteAction = OperationAction.Cascade; } AssociationEndMember pkEnd = CreateAssociationEnd( session, pkEntityType, pkMultiplicity, usedEndNames, onDeleteAction); RelationshipMultiplicity fkMultiplicity = RelationshipMultiplicity.Many; if ( !someFkColmnsAreNullable && AreRelationshipColumnsTheTypesEntireKey(fkEntityType, columns, r => r.FKColumn)) { // both the pk and fk side columns are the keys of their types // so this is a 1 to one relationship fkMultiplicity = RelationshipMultiplicity.ZeroOrOne; } AssociationEndMember fkEnd = CreateAssociationEnd(session, fkEntityType, fkMultiplicity, usedEndNames, OperationAction.None); // create the type string typeName = session.UsedTypeNames.AdjustIdentifier(firstRow.RelationshipName); AssociationType type = new AssociationType(typeName, _namespaceName, false, DataSpace.SSpace); type.AddMember(pkEnd); type.AddMember(fkEnd); List<EdmSchemaError> errors = new List<EdmSchemaError>(); bool isValid = CreateReferentialConstraint(session, type, pkEnd, fkEnd, columns, errors); string errorMessage; // We can skip most validation checks if the FKs are directly surfaced (since we can produce valid mappings in these cases). if (!this.GenerateForeignKeyProperties) { if (IsFkPartiallyContainedInPK(type, out errorMessage)) { errors.Add(new EdmSchemaError( errorMessage, (int)ModelBuilderErrorCode.UnsupportedForeinKeyPattern, EdmSchemaErrorSeverity.Warning)); isValid = false; } if (isValid) { //Now check if any FK (which could also be a PK) is shared among multiple Associations (ie shared via foreign key constraint). // To do this we check if the Association Type being generated has any dependent property which is also a dependent in one of the association typed already added. //If so, we keep one Association and throw the rest away. foreach (var toPropertyOfAddedAssociation in session.AssociationTypes.SelectMany(t => t.ReferentialConstraints.SelectMany(refconst => refconst.ToProperties))) { foreach (var toProperty in type.ReferentialConstraints.SelectMany(refconst => refconst.ToProperties)) { if (toProperty.DeclaringType.Equals(toPropertyOfAddedAssociation.DeclaringType) && toProperty.Equals(toPropertyOfAddedAssociation)) { errors.Add(new EdmSchemaError( Strings.SharedForeignKey(type.Name, toProperty, toProperty.DeclaringType), (int)ModelBuilderErrorCode.SharedForeignKey, EdmSchemaErrorSeverity.Warning)); isValid = false; break; } } if (!isValid) { break; } } } } if (isValid) { session.AssociationTypes.Add(type); } else { session.InvalidTypes.Add(type); session.RelationshipEndTypeLookup.Remove(pkEnd); session.RelationshipEndTypeLookup.Remove(fkEnd); } type.SetReadOnly(); session.AddErrorsForType(type, errors); }
private void CreateEdmFunction(LoadMethodSessionState session, DbObjectKey functionKey, List<FunctionDetailsReader.Memento> parameters) { Debug.Assert(parameters.Count != 0, "don't call the method with no data"); FunctionDetailsReader row = parameters[0].CreateReader(); FunctionParameter returnParameter = null; bool isValid = true; List<EdmSchemaError> errors = new List<EdmSchemaError>(); if (row.ReturnType != null) { Debug.Assert(!row.IsTvf, "TVF can't have ReturnType (used only for scalars)."); bool excludedForTarget; TypeUsage returnType = GetScalarFunctionTypeUsage(session, row.ReturnType, out excludedForTarget); if (returnType != null) { returnParameter = new FunctionParameter(EdmConstants.ReturnType, returnType, ParameterMode.ReturnValue); } else { isValid = false; errors.Add(new EdmSchemaError(excludedForTarget ? Strings.UnsupportedFunctionReturnDataTypeForTarget(row.ProcedureName, row.ReturnType) : Strings.UnsupportedFunctionReturnDataType(row.ProcedureName, row.ReturnType), (int)ModelBuilderErrorCode.UnsupportedType, EdmSchemaErrorSeverity.Warning)); } } else if (row.IsTvf) { if (_targetEntityFrameworkVersion < EntityFrameworkVersions.Version3) { return; } RowType tvfReturnType; if (session.TryGetTvfReturnType(functionKey, out tvfReturnType) && !session.InvalidTypes.Contains(tvfReturnType)) { var collectionType = tvfReturnType.GetCollectionType(); collectionType.SetReadOnly(); returnParameter = new FunctionParameter(EdmConstants.ReturnType, TypeUsage.Create(collectionType), ParameterMode.ReturnValue); } else { isValid = false; // If the TVF return type exists, but it is not valid, then reassign all its errors directly to the TVF. // This is needed in order to avoid the following kind of error reporting: // SSDL: // // <!-- Errors found while generating type: // column1 type not supported // column2 type not supported // <RowType /> // --> // ... // ... // <!-- Error found while generating type: // TableReferencedByTvfWasNotFound // <Function Name="TVF" .... /> // --> // // Instead we want something like this: // // <!-- Errors found while generating type: // column1 type not supported // column2 type not supported // TableReferencedByTvfWasNotFound // <Function Name="TVF" .... /> // --> // List<EdmSchemaError> tvfReturnTypeErrors; if (tvfReturnType != null && session.ItemToErrorsMap.TryGetValue(tvfReturnType, out tvfReturnTypeErrors)) { errors.AddRange(tvfReturnTypeErrors); session.ItemToErrorsMap.Remove(tvfReturnType); if (session.InvalidTypes.Contains(tvfReturnType)) { session.InvalidTypes.Remove(tvfReturnType); } } errors.Add(new EdmSchemaError( Strings.TableReferencedByTvfWasNotFound(functionKey), (int)ModelBuilderErrorCode.MissingTvfReturnTable, EdmSchemaErrorSeverity.Warning)); } } bool caseSensitive = false; UniqueIdentifierService uniqueIdentifiers = new UniqueIdentifierService(caseSensitive); List<FunctionParameter> functionParameters = new List<FunctionParameter>(); for (int i = 0; i < parameters.Count && !row.IsParameterNameNull; i++) { row.Attach(parameters[i]); TypeUsage parameterType = null; bool excludedForTarget = false; if (!row.IsParameterTypeNull) { parameterType = GetScalarFunctionTypeUsage(session, row.ParameterType, out excludedForTarget); } if (parameterType != null) { ParameterMode mode; if (!row.TryGetParameterMode(out mode)) { isValid = false; string modeValue = "null"; if (!row.IsParameterModeNull) { modeValue = row.ProcParameterMode; } errors.Add(new EdmSchemaError( Strings.ParameterDirectionNotValid( row.ProcedureName, row.ParameterName, modeValue), (int)ModelBuilderErrorCode.ParameterDirectionNotValid, EdmSchemaErrorSeverity.Warning)); } // the mode will get defaulted to something, so it is ok to keep creating after // an error getting the mode value. string parameterName = EntityModelSchemaGenerator.CreateValidEcmaName(row.ParameterName, 'p'); parameterName = uniqueIdentifiers.AdjustIdentifier(parameterName); FunctionParameter parameter = new FunctionParameter(parameterName, parameterType, mode); functionParameters.Add(parameter); } else { isValid = false; string typeValue = "null"; if (!row.IsParameterTypeNull) { typeValue = row.ParameterType; } errors.Add(new EdmSchemaError(excludedForTarget ? Strings.UnsupportedFunctionParameterDataTypeForTarget(row.ProcedureName, row.ParameterName, i, typeValue) : Strings.UnsupportedFunctionParameterDataType(row.ProcedureName, row.ParameterName, i, typeValue), (int)ModelBuilderErrorCode.UnsupportedType, EdmSchemaErrorSeverity.Warning)); } } string functionName = EntityModelSchemaGenerator.CreateValidEcmaName(row.ProcedureName, 'f'); functionName = session.UsedTypeNames.AdjustIdentifier(functionName); FunctionParameter[] returnParameters = returnParameter == null ? new FunctionParameter[0] : new FunctionParameter[] {returnParameter}; EdmFunction function = new EdmFunction(functionName, _namespaceName, DataSpace.SSpace, new EdmFunctionPayload { Schema = row.Schema, StoreFunctionName = functionName != row.ProcedureName ? row.ProcedureName : null, IsAggregate = row.IsIsAggregate, IsBuiltIn = row.IsBuiltIn, IsNiladic = row.IsNiladic, IsComposable = row.IsComposable, ReturnParameters = returnParameters, Parameters = functionParameters.ToArray() }); function.SetReadOnly(); session.AddErrorsForType(function, errors); if (isValid) { session.Functions.Add(function); } else { session.InvalidTypes.Add(function); } }
private void CreateTvfReturnRowType( LoadMethodSessionState session, IList<TableDetailsRow> columns, ICollection<string> primaryKeys, DbObjectType objectType, List<EdmSchemaError> errors) { Debug.Assert(columns.Count != 0, "Trying to create a RowType with 0 properties"); Debug.Assert(primaryKeys != null, "primaryKeys != null"); DbObjectKey tableKey = columns[0].CreateDbObjectKey(objectType); if (errors == null) { errors = new List<EdmSchemaError>(); } IList<string> excludedColumns; var properties = CreateEdmProperties(session, columns, tableKey, errors, out excludedColumns); var rowType = new RowType(properties); rowType.SetReadOnly(); session.AddTvfReturnType(tableKey, rowType); if (rowType.Properties.Count == 0) { session.InvalidTypes.Add(rowType); } session.AddErrorsForType(rowType, errors); }
private IList<EdmSchemaError> DoGenerateStoreMetadata(IEnumerable<EntityStoreSchemaFilterEntry> filters, Version targetEntityFrameworkVersion) { if (_entityContainer != null) { _entityContainer = null; _storeItemCollection = null; _errorsLookup = null; _invalidTypes = null; } _targetEntityFrameworkVersion = targetEntityFrameworkVersion; LoadMethodSessionState session = new LoadMethodSessionState(targetEntityFrameworkVersion); try { _loader.Open(); DbConnection connection = _loader.InnerConnection; DbProviderFactory providerFactory = DbProviderServices.GetProviderFactory(_loader.ProviderInvariantName); DbProviderServices providerServices = DbProviderServices.GetProviderServices(providerFactory); _providerManifestToken = providerServices.GetProviderManifestToken(connection); DbProviderManifest storeManifest = providerServices.GetProviderManifest(_providerManifestToken); session.Filters = filters; Debug.Assert(_namespaceName != null, "_namespaceName should not be null at this point, did you add a new ctor?"); session.ItemCollection = new StoreItemCollection(providerFactory, providerServices.GetProviderManifest(_providerManifestToken), _providerManifestToken); CreateTableEntityTypes(session); CreateViewEntityTypes(session); string entityContainerName = this._namespaceName.Replace(".", string.Empty) + CONTAINER_SUFFIX; Debug.Assert(entityContainerName != null, "We should always have a container name"); EntityContainer entityContainer = new EntityContainer(entityContainerName, DataSpace.SSpace); foreach (EntityType type in session.GetAllEntities()) { Debug.Assert(type.KeyMembers.Count > 0, "Why do we have Entities without keys in our valid Entities collection"); session.ItemCollection.AddInternal(type); EntitySet entitySet = CreateEntitySet(session, type); session.EntityTypeToSet.Add(type, entitySet); entityContainer.AddEntitySetBase(entitySet); } CreateAssociationTypes(session); foreach (AssociationType type in session.AssociationTypes) { session.ItemCollection.AddInternal(type); AssociationSet set = CreateAssociationSet(session, type); entityContainer.AddEntitySetBase(set); } entityContainer.SetReadOnly(); session.ItemCollection.AddInternal(entityContainer); FixupKeylessEntitySets(entityContainer, session); if (_targetEntityFrameworkVersion >= EntityFrameworkVersions.Version3 && _loader.StoreSchemaModelVersion >= EntityFrameworkVersions.Version3) { CreateTvfReturnRowTypes(session); } CreateEdmFunctions(session); foreach (EdmFunction function in session.Functions) { session.ItemCollection.AddInternal(function); } if (!HasErrorSeverityErrors(session.Errors)) { _entityContainer = entityContainer; _storeItemCollection = session.ItemCollection; _errorsLookup = session.ItemToErrorsMap; _invalidTypes = new List<EdmType>(session.InvalidTypes); } } catch (Exception e) { if (MetadataUtil.IsCatchableExceptionType(e)) { string message = EDesignUtil.GetMessagesFromEntireExceptionChain(e); session.AddErrorsForType(null, new EdmSchemaError(message, (int)ModelBuilderErrorCode.UnknownError, EdmSchemaErrorSeverity.Error, e)); } else { throw; } } finally { _loader.Close(); } return new List<EdmSchemaError>(session.Errors); }
private void CreateEntityType( LoadMethodSessionState session, IList<TableDetailsRow> columns, ICollection<string> primaryKeys, DbObjectType objectType, List<EdmSchemaError> errors) { Debug.Assert(columns.Count != 0, "Trying to create an EntityType with 0 properties"); Debug.Assert(primaryKeys != null, "primaryKeys != null"); DbObjectKey tableKey = columns[0].CreateDbObjectKey(objectType); if (errors == null) { errors = new List<EdmSchemaError>(); } // // Handle Tables without explicit declaration of keys // EntityCreationStatus status = EntityCreationStatus.Normal; if (primaryKeys.Count == 0) { List<string> pKeys = new List<string>(columns.Count); session.AddTableWithoutKey(tableKey); if (InferKeyColumns(session, columns, pKeys, tableKey, ref primaryKeys)) { errors.Add(new EdmSchemaError( Strings.NoPrimaryKeyDefined(tableKey), (int)ModelBuilderErrorCode.NoPrimaryKeyDefined, EdmSchemaErrorSeverity.Warning)); status = EntityCreationStatus.ReadOnly; } else { errors.Add(new EdmSchemaError( Strings.CannotCreateEntityWithNoPrimaryKeyDefined(tableKey), (int)ModelBuilderErrorCode.CannotCreateEntityWithoutPrimaryKey, EdmSchemaErrorSeverity.Warning)); status = EntityCreationStatus.Invalid; } } Debug.Assert(primaryKeys == null || primaryKeys.Count > 0,"There must be at least one key columns at this point in time"); IList<string> excludedColumns; var properties = CreateEdmProperties(session, columns, tableKey, errors, out excludedColumns); var excludedKeyColumns = (primaryKeys != null ? primaryKeys.Intersect(excludedColumns) : new string[0]).ToArray(); if (primaryKeys != null && excludedKeyColumns.Length == 0) { foreach (EdmMember pkColumn in properties.Where(p => primaryKeys.Contains(p.Name))) { if (!MetadataUtil.IsValidKeyType(_targetEntityFrameworkVersion, pkColumn.TypeUsage.EdmType)) { // make it a read-only table by calling this method recursively with no keys errors = new List<EdmSchemaError>(); var tableColumn = columns.Where(c => c.ColumnName == pkColumn.Name).Single(); errors.Add(new EdmSchemaError(Strings.InvalidTypeForPrimaryKey(tableColumn.GetMostQualifiedTableName(), tableColumn.ColumnName, tableColumn.DataType), (int)ModelBuilderErrorCode.InvalidKeyTypeFound, EdmSchemaErrorSeverity.Warning)); string[] keyColumns = new string[0]; CreateEntityType(session, columns, keyColumns, objectType, errors); return; } } } if (excludedKeyColumns.Length > 0) { // see if we have any keys left if (primaryKeys != null && excludedKeyColumns.Length < primaryKeys.Count) { primaryKeys = primaryKeys.Except(excludedKeyColumns).ToList(); status = EntityCreationStatus.ReadOnly; } else { primaryKeys = null; status = EntityCreationStatus.Invalid; } foreach (string columnName in excludedKeyColumns) { if (status == EntityCreationStatus.ReadOnly) { errors.Add(new EdmSchemaError( Strings.ExcludedColumnWasAKeyColumnEntityIsReadOnly(columnName, columns[0].GetMostQualifiedTableName()), (int)ModelBuilderErrorCode.ExcludedColumnWasAKeyColumn, EdmSchemaErrorSeverity.Warning)); } else { Debug.Assert(status == EntityCreationStatus.Invalid, "Did we change some code above to make it possible to be something different?"); errors.Add(new EdmSchemaError( Strings.ExcludedColumnWasAKeyColumnEntityIsInvalid(columnName, columns[0].GetMostQualifiedTableName()), (int)ModelBuilderErrorCode.ExcludedColumnWasAKeyColumn, EdmSchemaErrorSeverity.Warning)); } } } string typeName = session.UsedTypeNames.AdjustIdentifier(columns[0].TableName); var entityType = new EntityType(typeName, _namespaceName, DataSpace.SSpace, primaryKeys, properties); entityType.SetReadOnly(); switch (status) { case EntityCreationStatus.Normal: session.AddEntity(tableKey, entityType); break; case EntityCreationStatus.ReadOnly: session.AddEntity(tableKey, entityType); session.ReadOnlyEntities.Add(entityType); break; default: Debug.Assert(status == EntityCreationStatus.Invalid, "did you add a new value?"); session.InvalidTypes.Add(entityType); break; } session.AddErrorsForType(entityType, errors); }