1 //--------------------------------------------------------------------- 2 // <copyright file="EntityStoreSchemaGenerator.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // 6 // @owner Microsoft 7 // @backupOwner Microsoft 8 //--------------------------------------------------------------------- 9 using System.Collections.Generic; 10 using System.Diagnostics; 11 using System.Xml; 12 using SOM=System.Data.EntityModel; 13 using System.Globalization; 14 using System.Data.Metadata.Edm; 15 using System.Data.Entity.Design.Common; 16 using System.Data.Entity.Design.SsdlGenerator; 17 using System.Data.Common; 18 using System.Data.EntityClient; 19 using System.IO; 20 using System.Data.Mapping; 21 using System.Data.Common.Utils; 22 using System.Collections.ObjectModel; 23 using System.Data.Common.CommandTrees; 24 using System.Data.Common.CommandTrees.ExpressionBuilder; 25 using System.Text; 26 using System.Linq; 27 using Microsoft.Build.Utilities; 28 29 namespace System.Data.Entity.Design 30 { 31 /// <summary> 32 /// Responsible for Loading Database Schema Information 33 /// </summary> 34 public sealed partial class EntityStoreSchemaGenerator 35 { 36 private const string CONTAINER_SUFFIX = "Container"; 37 private EntityStoreSchemaGeneratorDatabaseSchemaLoader _loader; 38 private readonly string _provider; 39 private string _providerManifestToken = string.Empty; 40 private EntityContainer _entityContainer = null; 41 private StoreItemCollection _storeItemCollection; 42 private string _namespaceName; 43 private MetadataItemSerializer.ErrorsLookup _errorsLookup; 44 private List<EdmType> _invalidTypes; 45 private Version _targetEntityFrameworkVersion; 46 47 /// <summary> 48 /// Creates a new EntityStoreGenerator 49 /// </summary> 50 /// <param name="providerInvariantName">The name of the provider to use to load the schema information.</param> 51 /// <param name="connectionString">A connection string to the DB that should be loaded from.</param> 52 /// <param name="namespaceName">The namespace name to use for the store metadata that is generated.</param> EntityStoreSchemaGenerator(string providerInvariantName, string connectionString, string namespaceName)53 public EntityStoreSchemaGenerator(string providerInvariantName, string connectionString, string namespaceName) 54 { 55 EDesignUtil.CheckStringArgument(providerInvariantName, "providerInvariantName"); 56 EDesignUtil.CheckArgumentNull(connectionString, "connectionString"); // check for NULL string and support empty connection string 57 EDesignUtil.CheckStringArgument(namespaceName, "namespaceName"); 58 59 _namespaceName = namespaceName; 60 61 _provider = providerInvariantName; 62 _loader = new EntityStoreSchemaGeneratorDatabaseSchemaLoader(providerInvariantName, connectionString); 63 } 64 65 /// <summary> 66 /// Gets the EntityContainer that was created 67 /// </summary> 68 public EntityContainer EntityContainer 69 { 70 get 71 { 72 return _entityContainer; 73 } 74 } 75 76 /// <summary> 77 /// Gets the StoreItemCollection that was created 78 /// </summary> 79 [CLSCompliant(false)] 80 public StoreItemCollection StoreItemCollection 81 { 82 get 83 { 84 return _storeItemCollection; 85 } 86 } 87 88 /// <summary> 89 /// Indicates whether the given storage model will be used to produce an entity model with foreign keys. 90 /// </summary> 91 public bool GenerateForeignKeyProperties 92 { 93 get; 94 set; 95 } 96 97 /// <summary> 98 /// Creates a Metadata schema from the DbSchemaLoader that was passed in 99 /// </summary> 100 /// <returns>The new metadata for the schema that was loaded</returns> GenerateStoreMetadata()101 public IList<EdmSchemaError> GenerateStoreMetadata() 102 { 103 List<EntityStoreSchemaFilterEntry> filters = new List<EntityStoreSchemaFilterEntry>(); 104 return DoGenerateStoreMetadata(filters, EntityFrameworkVersions.Latest); 105 } 106 107 /// <summary> 108 /// Creates a Metadata schema from the DbSchemaLoader that was passed in 109 /// </summary> 110 /// <param name="filters">The filters to be applied during generation.</param> 111 /// <returns>The new metadata for the schema that was loaded</returns> GenerateStoreMetadata(IEnumerable<EntityStoreSchemaFilterEntry> filters)112 public IList<EdmSchemaError> GenerateStoreMetadata(IEnumerable<EntityStoreSchemaFilterEntry> filters) 113 { 114 EDesignUtil.CheckArgumentNull(filters, "filters"); 115 return DoGenerateStoreMetadata(filters, EntityFrameworkVersions.Latest); 116 } 117 118 /// <summary> 119 /// Creates a Metadata schema from the DbSchemaLoader that was passed in 120 /// </summary> 121 /// <param name="filters">The filters to be applied during generation.</param> 122 /// <param name="targetFrameworkMoniker">The filters to be applied during generation.</param> 123 /// <returns>The new metadata for the schema that was loaded</returns> GenerateStoreMetadata(IEnumerable<EntityStoreSchemaFilterEntry> filters, Version targetEntityFrameworkVersion)124 public IList<EdmSchemaError> GenerateStoreMetadata(IEnumerable<EntityStoreSchemaFilterEntry> filters, Version targetEntityFrameworkVersion) 125 { 126 EDesignUtil.CheckArgumentNull(filters, "filters"); 127 EDesignUtil.CheckTargetEntityFrameworkVersionArgument(targetEntityFrameworkVersion, "targetEntityFrameworkVersion"); 128 129 130 // we are not going to actually use targetFrameworkMoniker at this time, but 131 // we want the option to use it in the future if we change the 132 // the ssdl schema 133 return DoGenerateStoreMetadata(filters, targetEntityFrameworkVersion); 134 } 135 DoGenerateStoreMetadata(IEnumerable<EntityStoreSchemaFilterEntry> filters, Version targetEntityFrameworkVersion)136 private IList<EdmSchemaError> DoGenerateStoreMetadata(IEnumerable<EntityStoreSchemaFilterEntry> filters, Version targetEntityFrameworkVersion) 137 { 138 if (_entityContainer != null) 139 { 140 _entityContainer = null; 141 _storeItemCollection = null; 142 _errorsLookup = null; 143 _invalidTypes = null; 144 } 145 146 _targetEntityFrameworkVersion = targetEntityFrameworkVersion; 147 LoadMethodSessionState session = new LoadMethodSessionState(targetEntityFrameworkVersion); 148 try 149 { 150 _loader.Open(); 151 152 DbConnection connection = _loader.InnerConnection; 153 DbProviderFactory providerFactory = DbProviderServices.GetProviderFactory(_loader.ProviderInvariantName); 154 DbProviderServices providerServices = DbProviderServices.GetProviderServices(providerFactory); 155 _providerManifestToken = providerServices.GetProviderManifestToken(connection); 156 DbProviderManifest storeManifest = providerServices.GetProviderManifest(_providerManifestToken); 157 158 session.Filters = filters; 159 Debug.Assert(_namespaceName != null, "_namespaceName should not be null at this point, did you add a new ctor?"); 160 161 session.ItemCollection = new StoreItemCollection(providerFactory, providerServices.GetProviderManifest(_providerManifestToken), _providerManifestToken); 162 163 CreateTableEntityTypes(session); 164 CreateViewEntityTypes(session); 165 string entityContainerName = this._namespaceName.Replace(".", string.Empty) + CONTAINER_SUFFIX; 166 167 Debug.Assert(entityContainerName != null, "We should always have a container name"); 168 EntityContainer entityContainer = new EntityContainer(entityContainerName, DataSpace.SSpace); 169 170 foreach (EntityType type in session.GetAllEntities()) 171 { 172 Debug.Assert(type.KeyMembers.Count > 0, "Why do we have Entities without keys in our valid Entities collection"); 173 session.ItemCollection.AddInternal(type); 174 EntitySet entitySet = CreateEntitySet(session, type); 175 session.EntityTypeToSet.Add(type, entitySet); 176 entityContainer.AddEntitySetBase(entitySet); 177 } 178 179 CreateAssociationTypes(session); 180 foreach (AssociationType type in session.AssociationTypes) 181 { 182 session.ItemCollection.AddInternal(type); 183 AssociationSet set = CreateAssociationSet(session, type); 184 entityContainer.AddEntitySetBase(set); 185 } 186 187 entityContainer.SetReadOnly(); 188 session.ItemCollection.AddInternal(entityContainer); 189 FixupKeylessEntitySets(entityContainer, session); 190 191 if (_targetEntityFrameworkVersion >= EntityFrameworkVersions.Version3 && 192 _loader.StoreSchemaModelVersion >= EntityFrameworkVersions.Version3) 193 { 194 CreateTvfReturnRowTypes(session); 195 } 196 CreateEdmFunctions(session); 197 foreach (EdmFunction function in session.Functions) 198 { 199 session.ItemCollection.AddInternal(function); 200 } 201 202 if (!HasErrorSeverityErrors(session.Errors)) 203 { 204 _entityContainer = entityContainer; 205 _storeItemCollection = session.ItemCollection; 206 _errorsLookup = session.ItemToErrorsMap; 207 _invalidTypes = new List<EdmType>(session.InvalidTypes); 208 } 209 } 210 catch (Exception e) 211 { 212 if (MetadataUtil.IsCatchableExceptionType(e)) 213 { 214 string message = EDesignUtil.GetMessagesFromEntireExceptionChain(e); 215 session.AddErrorsForType(null, 216 new EdmSchemaError(message, 217 (int)ModelBuilderErrorCode.UnknownError, 218 EdmSchemaErrorSeverity.Error, 219 e)); 220 } 221 else 222 { 223 throw; 224 } 225 } 226 finally 227 { 228 _loader.Close(); 229 } 230 231 return new List<EdmSchemaError>(session.Errors); 232 } 233 234 /// <summary> 235 /// Writes the Schema to xml 236 /// </summary> 237 /// <param name="outputFileName">The name of the file to write the xml to.</param> WriteStoreSchema(string outputFileName)238 public void WriteStoreSchema(string outputFileName) 239 { 240 EDesignUtil.CheckStringArgument(outputFileName, "outputFileName"); 241 CheckValidItemCollection(); 242 243 XmlWriterSettings settings = new XmlWriterSettings(); 244 settings.Indent = true; 245 using (XmlWriter writer = XmlWriter.Create(outputFileName, settings)) 246 { 247 WriteStoreSchema(writer); 248 } 249 } 250 251 /// <summary> 252 /// Writes the Schema to xml. 253 /// </summary> 254 /// <param name="writer">The XmlWriter to write the xml to.</param> WriteStoreSchema(XmlWriter writer)255 public void WriteStoreSchema(XmlWriter writer) 256 { 257 EDesignUtil.CheckArgumentNull(writer, "writer"); 258 CheckValidItemCollection(); 259 260 // we are going to add this EntityStoreSchemaGenerator namespace at the top of 261 // the file so that when we mark the entitysets with where they came from 262 // we don't have to repeat the namespace on each node. The VS tools use 263 // the source information to give better messages when refreshing the .ssdl from the db 264 // e.g. 265 // <Schema xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator ...> 266 // <EntityContainer ...> 267 // <!-- Views is the name of the StoreInformation EntitySet that this EntitySet was created from --> 268 // <EntitySet ... store:SchemaInformationSource="Views" /> 269 // </EntityContainer> 270 // ... 271 // </Schema> 272 var xmlPrefixToNamespace = new KeyValuePair<string, string>("store", DesignXmlConstants.EntityStoreSchemaGeneratorNamespace); 273 MetadataItemSerializer.WriteXml(writer, StoreItemCollection, _namespaceName, _errorsLookup, _invalidTypes, _provider, _providerManifestToken, _targetEntityFrameworkVersion, xmlPrefixToNamespace); 274 } 275 276 /// <summary> 277 /// Creates an EntityConnection loaded with the providers metadata for the store schema. 278 /// Store schema model is the one used in <see cref="EntityFrameworkVersions.Version2"/>. 279 /// </summary> 280 /// <param name="providerInvariantName">The provider invariant name.</param> 281 /// <param name="connectionString">The connection for the providers connection.</param> 282 /// <returns>An EntityConnection that can query the ConceptualSchemaDefinition for the provider.</returns> CreateStoreSchemaConnection(string providerInvariantName, string connectionString)283 public static EntityConnection CreateStoreSchemaConnection(string providerInvariantName, string connectionString) 284 { 285 return CreateStoreSchemaConnection(providerInvariantName, connectionString, EntityFrameworkVersions.Version2); 286 } 287 288 /// <summary> 289 /// Creates an EntityConnection loaded with the providers metadata for the store schema. 290 /// Note that the targetEntityFrameworkVersion parameter uses internal EntityFramework version numbers as 291 /// described in the <see cref="EntityFrameworkVersions"/> class. 292 /// </summary> 293 /// <param name="providerInvariantName">The provider invariant name.</param> 294 /// <param name="connectionString">The connection for the providers connection.</param> 295 /// <param name="targetEntityFrameworkVersion">The internal Entity Framework version that is being targeted.</param> 296 /// <returns>An EntityConnection that can query the ConceptualSchemaDefinition for the provider.</returns> CreateStoreSchemaConnection(string providerInvariantName, string connectionString, Version targetEntityFrameworkVersion)297 public static EntityConnection CreateStoreSchemaConnection(string providerInvariantName, string connectionString, Version targetEntityFrameworkVersion) 298 { 299 EDesignUtil.CheckArgumentNull(providerInvariantName, "providerInvariantName"); 300 EDesignUtil.CheckArgumentNull(connectionString, "connectionString"); 301 EDesignUtil.CheckTargetEntityFrameworkVersionArgument(targetEntityFrameworkVersion, "targetEntityFrameworkVersion"); 302 303 DbProviderFactory factory; 304 try 305 { 306 factory = DbProviderFactories.GetFactory(providerInvariantName); 307 } 308 catch (ArgumentException e) 309 { 310 throw EDesignUtil.Argument(Strings.EntityClient_InvalidStoreProvider(providerInvariantName), e); 311 } 312 313 DbProviderServices providerServices = MetadataUtil.GetProviderServices(factory); 314 315 DbConnection providerConnection = factory.CreateConnection(); 316 if (providerConnection == null) 317 { 318 throw EDesignUtil.ProviderIncompatible(Strings.ProviderFactoryReturnedNullFactory(providerInvariantName)); 319 } 320 providerConnection.ConnectionString = connectionString; 321 322 MetadataWorkspace workspace = GetProviderSchemaMetadataWorkspace(providerServices, providerConnection, targetEntityFrameworkVersion); 323 324 // create the connection with the information we have 325 return new EntityConnection(workspace, providerConnection); 326 } 327 GetProviderSchemaMetadataWorkspace(DbProviderServices providerServices, DbConnection providerConnection, Version targetEntityFrameworkVersion)328 private static MetadataWorkspace GetProviderSchemaMetadataWorkspace(DbProviderServices providerServices, DbConnection providerConnection, Version targetEntityFrameworkVersion) 329 { 330 XmlReader csdl = null; 331 XmlReader ssdl = null; 332 XmlReader msl = null; 333 334 Debug.Assert(EntityFrameworkVersions.IsValidVersion(targetEntityFrameworkVersion), "EntityFrameworkVersions.IsValidVersion(targetEntityFrameworkVersion)"); 335 string csdlName; 336 string ssdlName; 337 string mslName; 338 if (targetEntityFrameworkVersion >= EntityFrameworkVersions.Version3) 339 { 340 csdlName = DbProviderManifest.ConceptualSchemaDefinitionVersion3; 341 ssdlName = DbProviderManifest.StoreSchemaDefinitionVersion3; 342 mslName = DbProviderManifest.StoreSchemaMappingVersion3; 343 } 344 else 345 { 346 csdlName = DbProviderManifest.ConceptualSchemaDefinition; 347 ssdlName = DbProviderManifest.StoreSchemaDefinition; 348 mslName = DbProviderManifest.StoreSchemaMapping; 349 } 350 351 try 352 { 353 // create the metadata workspace 354 MetadataWorkspace workspace = new MetadataWorkspace(); 355 356 string manifestToken = providerServices.GetProviderManifestToken(providerConnection); 357 DbProviderManifest providerManifest = providerServices.GetProviderManifest(manifestToken); 358 359 // create the EdmItemCollection 360 IList<EdmSchemaError> errors; 361 ssdl = providerManifest.GetInformation(ssdlName); 362 string location = Strings.DbProviderServicesInformationLocationPath(providerConnection.GetType().Name, ssdlName); 363 List<string> ssdlLocations = new List<string>(1); 364 ssdlLocations.Add(location); 365 StoreItemCollection storeItemCollection = new StoreItemCollection(new XmlReader[] { ssdl }, ssdlLocations.AsReadOnly(), out errors); 366 ThrowOnError(errors); 367 workspace.RegisterItemCollection(storeItemCollection); 368 369 csdl = DbProviderServices.GetConceptualSchemaDefinition(csdlName); 370 location = Strings.DbProviderServicesInformationLocationPath(typeof(DbProviderServices).Name, csdlName); 371 List<string> csdlLocations = new List<string>(1); 372 csdlLocations.Add(location); 373 EdmItemCollection edmItemCollection = new EdmItemCollection(new XmlReader[] { csdl }, csdlLocations.AsReadOnly(), out errors); 374 ThrowOnError(errors); 375 workspace.RegisterItemCollection(edmItemCollection); 376 377 msl = providerManifest.GetInformation(mslName); 378 location = Strings.DbProviderServicesInformationLocationPath(providerConnection.GetType().Name, DbProviderManifest.StoreSchemaMapping); 379 List<string> mslLocations = new List<string>(1); 380 mslLocations.Add(location); 381 StorageMappingItemCollection mappingItemCollection = new StorageMappingItemCollection(edmItemCollection, 382 storeItemCollection, 383 new XmlReader[] { msl }, 384 mslLocations, 385 out errors); 386 ThrowOnError(errors); 387 workspace.RegisterItemCollection(mappingItemCollection); 388 389 // make the views generate here so we can wrap the provider schema problems 390 // in a ProviderIncompatibleException 391 ForceViewGeneration(workspace); 392 return workspace; 393 } 394 catch (ProviderIncompatibleException) 395 { 396 // we don't really want to catch this one, just rethrow it 397 throw; 398 } 399 catch (Exception e) 400 { 401 if (MetadataUtil.IsCatchableExceptionType(e)) 402 { 403 throw EDesignUtil.ProviderIncompatible(Strings.ProviderSchemaErrors, e); 404 } 405 406 throw; 407 } 408 finally 409 { 410 if (csdl != null) ((IDisposable)csdl).Dispose(); 411 if (ssdl != null) ((IDisposable)ssdl).Dispose(); 412 if (msl != null) ((IDisposable)msl).Dispose(); 413 } 414 } 415 ForceViewGeneration(MetadataWorkspace workspace)416 private static void ForceViewGeneration(MetadataWorkspace workspace) 417 { 418 ReadOnlyCollection<EntityContainer> containers = workspace.GetItems<EntityContainer>(DataSpace.SSpace); 419 Debug.Assert(containers.Count != 0, "no s space containers found"); 420 Debug.Assert(containers[0].BaseEntitySets.Count != 0, "no entity sets in the sspace container"); 421 workspace.GetCqtView(containers[0].BaseEntitySets[0]); 422 } 423 ThrowOnError(IList<EdmSchemaError> errors)424 private static void ThrowOnError(IList<EdmSchemaError> errors) 425 { 426 if (errors.Count != 0) 427 { 428 if (!MetadataUtil.CheckIfAllErrorsAreWarnings(errors)) 429 { 430 throw EDesignUtil.ProviderIncompatible(Strings.ProviderSchemaErrors, EntityUtil.InvalidSchemaEncountered(MetadataUtil.CombineErrorMessage(errors))); 431 } 432 } 433 } 434 CheckValidItemCollection()435 private void CheckValidItemCollection() 436 { 437 if (_entityContainer == null) 438 { 439 throw EDesignUtil.EntityStoreGeneratorSchemaNotLoaded(); 440 } 441 } 442 HasErrorSeverityErrors(IEnumerable<EdmSchemaError> errors)443 internal static bool HasErrorSeverityErrors(IEnumerable<EdmSchemaError> errors) 444 { 445 foreach (EdmSchemaError error in errors) 446 { 447 if (error.Severity == EdmSchemaErrorSeverity.Error) 448 { 449 return true; 450 } 451 } 452 return false; 453 } 454 455 CreateAssociationSet(LoadMethodSessionState session, AssociationType type)456 private AssociationSet CreateAssociationSet(LoadMethodSessionState session, 457 AssociationType type) 458 { 459 AssociationSet set = new AssociationSet(type.Name, type); 460 461 foreach(AssociationEndMember end in type.RelationshipEndMembers) 462 { 463 EntitySet entitySet = session.GetEntitySet(end); 464 DbObjectKey key = session.GetKey(entitySet.ElementType); 465 AssociationSetEnd setEnd = new AssociationSetEnd(entitySet, set, end); 466 set.AddAssociationSetEnd(setEnd); 467 } 468 469 set.SetReadOnly(); 470 return set; 471 } 472 CreateEntitySet( LoadMethodSessionState session, EntityType type )473 private EntitySet CreateEntitySet( 474 LoadMethodSessionState session, 475 EntityType type 476 ) 477 { 478 DbObjectKey key = session.GetKey(type); 479 string schema = key.Schema; 480 481 string table = null; 482 if (key.TableName != type.Name) 483 { 484 table = key.TableName; 485 } 486 487 EntitySet entitySet = new EntitySet(type.Name, 488 schema, 489 table, 490 null, 491 type); 492 493 MetadataProperty property = System.Data.EntityModel.SchemaObjectModel.SchemaElement.CreateMetadataPropertyFromOtherNamespaceXmlArtifact(DesignXmlConstants.EntityStoreSchemaGeneratorNamespace, DesignXmlConstants.EntityStoreSchemaGeneratorTypeAttributeName, GetSourceNameFromObjectType(key.ObjectType)); 494 List<MetadataProperty> properties = new List<MetadataProperty>(); 495 properties.Add(property); 496 entitySet.AddMetadataProperties(properties); 497 entitySet.SetReadOnly(); 498 return entitySet; 499 } 500 GetSourceNameFromObjectType(DbObjectType dbObjectType)501 private string GetSourceNameFromObjectType(DbObjectType dbObjectType) 502 { 503 switch(dbObjectType) 504 { 505 case DbObjectType.Table: 506 return DesignXmlConstants.TypeValueTables; 507 default: 508 Debug.Assert(dbObjectType == DbObjectType.View, "did you change to a call that could have different types?"); 509 return DesignXmlConstants.TypeValueViews; 510 } 511 } 512 CreateEdmFunctions(LoadMethodSessionState session)513 private void CreateEdmFunctions(LoadMethodSessionState session) 514 { 515 using(FunctionDetailsReader reader = _loader.LoadFunctionDetails(session.Filters)) 516 { 517 DbObjectKey currentFunction = new DbObjectKey(); 518 List<FunctionDetailsReader.Memento> parameters = new List<FunctionDetailsReader.Memento>(); 519 while(reader.Read()) 520 { 521 DbObjectKey rowFunction = reader.CreateDbObjectKey(); 522 if (rowFunction != currentFunction) 523 { 524 if (!currentFunction.IsEmpty) 525 { 526 CreateEdmFunction(session, currentFunction, parameters); 527 parameters.Clear(); 528 } 529 currentFunction = rowFunction; 530 } 531 parameters.Add(reader.CreateMemento()); 532 } 533 534 if (parameters.Count != 0) 535 { 536 CreateEdmFunction(session, currentFunction, parameters); 537 } 538 } 539 } 540 CreateEdmFunction(LoadMethodSessionState session, DbObjectKey functionKey, List<FunctionDetailsReader.Memento> parameters)541 private void CreateEdmFunction(LoadMethodSessionState session, DbObjectKey functionKey, List<FunctionDetailsReader.Memento> parameters) 542 { 543 Debug.Assert(parameters.Count != 0, "don't call the method with no data"); 544 545 FunctionDetailsReader row = parameters[0].CreateReader(); 546 547 FunctionParameter returnParameter = null; 548 bool isValid = true; 549 List<EdmSchemaError> errors = new List<EdmSchemaError>(); 550 if (row.ReturnType != null) 551 { 552 Debug.Assert(!row.IsTvf, "TVF can't have ReturnType (used only for scalars)."); 553 bool excludedForTarget; 554 TypeUsage returnType = GetScalarFunctionTypeUsage(session, row.ReturnType, out excludedForTarget); 555 if (returnType != null) 556 { 557 returnParameter = new FunctionParameter(EdmConstants.ReturnType, returnType, ParameterMode.ReturnValue); 558 } 559 else 560 { 561 isValid = false; 562 errors.Add(new EdmSchemaError(excludedForTarget ? 563 Strings.UnsupportedFunctionReturnDataTypeForTarget(row.ProcedureName, row.ReturnType) : 564 Strings.UnsupportedFunctionReturnDataType(row.ProcedureName, row.ReturnType), 565 (int)ModelBuilderErrorCode.UnsupportedType, 566 EdmSchemaErrorSeverity.Warning)); 567 } 568 } 569 else if (row.IsTvf) 570 { 571 if (_targetEntityFrameworkVersion < EntityFrameworkVersions.Version3) 572 { 573 return; 574 } 575 RowType tvfReturnType; 576 if (session.TryGetTvfReturnType(functionKey, out tvfReturnType) && !session.InvalidTypes.Contains(tvfReturnType)) 577 { 578 var collectionType = tvfReturnType.GetCollectionType(); 579 collectionType.SetReadOnly(); 580 returnParameter = new FunctionParameter(EdmConstants.ReturnType, TypeUsage.Create(collectionType), ParameterMode.ReturnValue); 581 } 582 else 583 { 584 isValid = false; 585 586 // If the TVF return type exists, but it is not valid, then reassign all its errors directly to the TVF. 587 // This is needed in order to avoid the following kind of error reporting: 588 // SSDL: 589 // 590 // <!-- Errors found while generating type: 591 // column1 type not supported 592 // column2 type not supported 593 // <RowType /> 594 // --> 595 // ... 596 // ... 597 // <!-- Error found while generating type: 598 // TableReferencedByTvfWasNotFound 599 // <Function Name="TVF" .... /> 600 // --> 601 // 602 // Instead we want something like this: 603 // 604 // <!-- Errors found while generating type: 605 // column1 type not supported 606 // column2 type not supported 607 // TableReferencedByTvfWasNotFound 608 // <Function Name="TVF" .... /> 609 // --> 610 // 611 612 List<EdmSchemaError> tvfReturnTypeErrors; 613 if (tvfReturnType != null && session.ItemToErrorsMap.TryGetValue(tvfReturnType, out tvfReturnTypeErrors)) 614 { 615 errors.AddRange(tvfReturnTypeErrors); 616 session.ItemToErrorsMap.Remove(tvfReturnType); 617 if (session.InvalidTypes.Contains(tvfReturnType)) 618 { 619 session.InvalidTypes.Remove(tvfReturnType); 620 } 621 } 622 623 errors.Add(new EdmSchemaError( 624 Strings.TableReferencedByTvfWasNotFound(functionKey), 625 (int)ModelBuilderErrorCode.MissingTvfReturnTable, 626 EdmSchemaErrorSeverity.Warning)); 627 } 628 } 629 630 bool caseSensitive = false; 631 UniqueIdentifierService uniqueIdentifiers = new UniqueIdentifierService(caseSensitive); 632 List<FunctionParameter> functionParameters = new List<FunctionParameter>(); 633 for (int i = 0; i < parameters.Count && !row.IsParameterNameNull; i++) 634 { 635 row.Attach(parameters[i]); 636 TypeUsage parameterType = null; 637 bool excludedForTarget = false; 638 if (!row.IsParameterTypeNull) 639 { 640 parameterType = GetScalarFunctionTypeUsage(session, row.ParameterType, out excludedForTarget); 641 } 642 643 if (parameterType != null) 644 { 645 ParameterMode mode; 646 if (!row.TryGetParameterMode(out mode)) 647 { 648 isValid = false; 649 string modeValue = "null"; 650 if (!row.IsParameterModeNull) 651 { 652 modeValue = row.ProcParameterMode; 653 } 654 errors.Add(new EdmSchemaError( 655 Strings.ParameterDirectionNotValid( 656 row.ProcedureName, 657 row.ParameterName, 658 modeValue), 659 (int)ModelBuilderErrorCode.ParameterDirectionNotValid, 660 EdmSchemaErrorSeverity.Warning)); 661 } 662 663 // the mode will get defaulted to something, so it is ok to keep creating after 664 // an error getting the mode value. 665 string parameterName = EntityModelSchemaGenerator.CreateValidEcmaName(row.ParameterName, 'p'); 666 parameterName = uniqueIdentifiers.AdjustIdentifier(parameterName); 667 FunctionParameter parameter = new FunctionParameter(parameterName, parameterType, mode); 668 functionParameters.Add(parameter); 669 } 670 else 671 { 672 isValid = false; 673 string typeValue = "null"; 674 if (!row.IsParameterTypeNull) 675 { 676 typeValue = row.ParameterType; 677 } 678 errors.Add(new EdmSchemaError(excludedForTarget ? 679 Strings.UnsupportedFunctionParameterDataTypeForTarget(row.ProcedureName, row.ParameterName, i, typeValue) : 680 Strings.UnsupportedFunctionParameterDataType(row.ProcedureName, row.ParameterName, i, typeValue), 681 (int)ModelBuilderErrorCode.UnsupportedType, 682 EdmSchemaErrorSeverity.Warning)); 683 } 684 } 685 686 string functionName = EntityModelSchemaGenerator.CreateValidEcmaName(row.ProcedureName, 'f'); 687 functionName = session.UsedTypeNames.AdjustIdentifier(functionName); 688 FunctionParameter[] returnParameters = 689 returnParameter == null ? new FunctionParameter[0] : new FunctionParameter[] {returnParameter}; 690 EdmFunction function = new EdmFunction(functionName, 691 _namespaceName, 692 DataSpace.SSpace, 693 new EdmFunctionPayload 694 { 695 Schema = row.Schema, 696 StoreFunctionName = functionName != row.ProcedureName ? row.ProcedureName : null, 697 IsAggregate = row.IsIsAggregate, 698 IsBuiltIn = row.IsBuiltIn, 699 IsNiladic = row.IsNiladic, 700 IsComposable = row.IsComposable, 701 ReturnParameters = returnParameters, 702 Parameters = functionParameters.ToArray() 703 }); 704 function.SetReadOnly(); 705 706 session.AddErrorsForType(function, errors); 707 if (isValid) 708 { 709 session.Functions.Add(function); 710 } 711 else 712 { 713 session.InvalidTypes.Add(function); 714 } 715 } 716 GetScalarFunctionTypeUsage(LoadMethodSessionState session, string dataType, out bool excludedForTarget)717 private TypeUsage GetScalarFunctionTypeUsage(LoadMethodSessionState session, string dataType, out bool excludedForTarget) 718 { 719 PrimitiveType primitiveType; 720 if (session.TryGetStorePrimitiveType(dataType, out primitiveType, out excludedForTarget)) 721 { 722 TypeUsage usage = TypeUsage.Create(primitiveType, FacetValues.NullFacetValues); 723 return usage; 724 } 725 return null; 726 } 727 CreateAssociationTypes(LoadMethodSessionState session)728 private void CreateAssociationTypes(LoadMethodSessionState session) 729 { 730 string currentRelationshipId = string.Empty; 731 List<RelationshipDetailsRow> columns = new List<RelationshipDetailsRow>(); 732 foreach (RelationshipDetailsRow row in _loader.LoadRelationships(session.Filters)) 733 { 734 string rowRelationshipId = row.RelationshipId; 735 if (rowRelationshipId != currentRelationshipId) 736 { 737 if (!string.IsNullOrEmpty(currentRelationshipId)) 738 { 739 CreateAssociationType(session, columns); 740 columns.Clear(); 741 } 742 currentRelationshipId = rowRelationshipId; 743 } 744 745 columns.Add(row); 746 } 747 748 if (!string.IsNullOrEmpty(currentRelationshipId)) 749 { 750 CreateAssociationType(session, columns); 751 } 752 } 753 CreateAssociationType(LoadMethodSessionState session, List<RelationshipDetailsRow> columns)754 private void CreateAssociationType(LoadMethodSessionState session, 755 List<RelationshipDetailsRow> columns) 756 { 757 Debug.Assert(columns.Count != 0, "should have at least one column"); 758 759 RelationshipDetailsRow firstRow = columns[0]; 760 761 // get the entity types for the ends 762 EntityType pkEntityType; 763 EntityType fkEntityType; 764 if (!TryGetEndEntities(session, firstRow, out pkEntityType, out fkEntityType)) 765 { 766 return; 767 } 768 769 if (!AreRelationshipColumnsTheTypesEntireKey(pkEntityType, columns, r => r.PKColumn)) 770 { 771 session.AddErrorsForType(pkEntityType, new EdmSchemaError(Strings.UnsupportedDbRelationship(firstRow.RelationshipName), (int)ModelBuilderErrorCode.UnsupportedDbRelationship, EdmSchemaErrorSeverity.Warning)); 772 return; 773 774 } 775 UniqueIdentifierService usedEndNames = new UniqueIdentifierService(false); 776 // figure out the lower bound of the pk end 777 bool someFkColmnsAreNullable; 778 if (_targetEntityFrameworkVersion == EntityFrameworkVersions.Version1) 779 { 780 someFkColmnsAreNullable = AreAllFkKeyColumnsNullable(fkEntityType, columns); 781 } 782 else 783 { 784 someFkColmnsAreNullable = AreAnyFkKeyColumnsNullable(fkEntityType, columns); 785 } 786 787 RelationshipMultiplicity pkMultiplicity = someFkColmnsAreNullable ? RelationshipMultiplicity.ZeroOrOne : RelationshipMultiplicity.One; 788 //Get the Delete Action for the end and set it. 789 //The only DeleteAction we support is Cascade, ignor all others for now. 790 OperationAction onDeleteAction = OperationAction.None; 791 if (firstRow.RelationshipIsCascadeDelete) 792 { 793 onDeleteAction = OperationAction.Cascade; 794 } 795 796 AssociationEndMember pkEnd = CreateAssociationEnd( session, 797 pkEntityType, 798 pkMultiplicity, 799 usedEndNames, onDeleteAction); 800 801 RelationshipMultiplicity fkMultiplicity = RelationshipMultiplicity.Many; 802 803 if ( !someFkColmnsAreNullable && 804 AreRelationshipColumnsTheTypesEntireKey(fkEntityType, columns, r => r.FKColumn)) 805 { 806 // both the pk and fk side columns are the keys of their types 807 // so this is a 1 to one relationship 808 fkMultiplicity = RelationshipMultiplicity.ZeroOrOne; 809 } 810 811 AssociationEndMember fkEnd = CreateAssociationEnd(session, 812 fkEntityType, 813 fkMultiplicity, 814 usedEndNames, OperationAction.None); 815 816 817 // create the type 818 string typeName = session.UsedTypeNames.AdjustIdentifier(firstRow.RelationshipName); 819 AssociationType type = new AssociationType(typeName, 820 _namespaceName, false, DataSpace.SSpace); 821 type.AddMember(pkEnd); 822 type.AddMember(fkEnd); 823 824 List<EdmSchemaError> errors = new List<EdmSchemaError>(); 825 bool isValid = CreateReferentialConstraint(session, 826 type, 827 pkEnd, 828 fkEnd, 829 columns, 830 errors); 831 832 833 string errorMessage; 834 835 // We can skip most validation checks if the FKs are directly surfaced (since we can produce valid mappings in these cases). 836 if (!this.GenerateForeignKeyProperties) 837 { 838 if (IsFkPartiallyContainedInPK(type, out errorMessage)) 839 { 840 errors.Add(new EdmSchemaError( 841 errorMessage, 842 (int)ModelBuilderErrorCode.UnsupportedForeinKeyPattern, 843 EdmSchemaErrorSeverity.Warning)); 844 isValid = false; 845 } 846 847 if (isValid) 848 { 849 //Now check if any FK (which could also be a PK) is shared among multiple Associations (ie shared via foreign key constraint). 850 // 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. 851 //If so, we keep one Association and throw the rest away. 852 853 foreach (var toPropertyOfAddedAssociation in session.AssociationTypes.SelectMany(t => t.ReferentialConstraints.SelectMany(refconst => refconst.ToProperties))) 854 { 855 foreach (var toProperty in type.ReferentialConstraints.SelectMany(refconst => refconst.ToProperties)) 856 { 857 if (toProperty.DeclaringType.Equals(toPropertyOfAddedAssociation.DeclaringType) && toProperty.Equals(toPropertyOfAddedAssociation)) 858 { 859 errors.Add(new EdmSchemaError( 860 Strings.SharedForeignKey(type.Name, toProperty, toProperty.DeclaringType), 861 (int)ModelBuilderErrorCode.SharedForeignKey, 862 EdmSchemaErrorSeverity.Warning)); 863 864 isValid = false; 865 break; 866 } 867 } 868 869 if (!isValid) 870 { 871 break; 872 } 873 } 874 } 875 } 876 877 if (isValid) 878 { 879 session.AssociationTypes.Add(type); 880 } 881 else 882 { 883 session.InvalidTypes.Add(type); 884 session.RelationshipEndTypeLookup.Remove(pkEnd); 885 session.RelationshipEndTypeLookup.Remove(fkEnd); 886 } 887 888 889 type.SetReadOnly(); 890 session.AddErrorsForType(type, errors); 891 } 892 TryGetEndEntities( LoadMethodSessionState session, RelationshipDetailsRow row, out EntityType pkEntityType, out EntityType fkEntityType)893 private bool TryGetEndEntities( 894 LoadMethodSessionState session, 895 RelationshipDetailsRow row, 896 out EntityType pkEntityType, 897 out EntityType fkEntityType) 898 { 899 RelationshipDetailsCollection table = row.Table; 900 DbObjectKey pkKey = new DbObjectKey(row[table.PKCatalogColumn], 901 row[table.PKSchemaColumn], 902 row[table.PKTableColumn], DbObjectType.Unknown); 903 DbObjectKey fkKey = new DbObjectKey(row[table.FKCatalogColumn], 904 row[table.FKSchemaColumn], 905 row[table.FKTableColumn], DbObjectType.Unknown); 906 907 bool worked = session.TryGetEntity(pkKey, out pkEntityType); 908 worked &= session.TryGetEntity(fkKey, out fkEntityType); 909 910 return worked; 911 } 912 AreRelationshipColumnsTheTypesEntireKey( EntityType entity, List<RelationshipDetailsRow> columns, Func<RelationshipDetailsRow, string> getColumnName)913 private static bool AreRelationshipColumnsTheTypesEntireKey( 914 EntityType entity, 915 List<RelationshipDetailsRow> columns, 916 Func<RelationshipDetailsRow, string> getColumnName) 917 { 918 if (entity.KeyMembers.Count != columns.Count) 919 { 920 // to be the entire key, 921 // must have the same number of columns 922 return false; 923 } 924 925 foreach (RelationshipDetailsRow row in columns) 926 { 927 if (!entity.KeyMembers.Contains(getColumnName(row))) 928 { 929 // not a key 930 return false; 931 } 932 } 933 return true; 934 } 935 AreAnyFkKeyColumnsNullable( EntityType entity, List<RelationshipDetailsRow> columns)936 private static bool AreAnyFkKeyColumnsNullable( 937 EntityType entity, 938 List<RelationshipDetailsRow> columns) 939 { 940 foreach (RelationshipDetailsRow row in columns) 941 { 942 EdmProperty property; 943 if (entity.Properties.TryGetValue(row.FKColumn, false, out property)) 944 { 945 if (property.Nullable) 946 { 947 return true; 948 } 949 } 950 else 951 { 952 Debug.Fail("Why didn't we find the column?"); 953 return false; 954 } 955 } 956 return false; 957 } 958 AreAllFkKeyColumnsNullable( EntityType entity, List<RelationshipDetailsRow> columns)959 private static bool AreAllFkKeyColumnsNullable( 960 EntityType entity, 961 List<RelationshipDetailsRow> columns) 962 { 963 foreach (RelationshipDetailsRow row in columns) 964 { 965 EdmProperty property; 966 if (entity.Properties.TryGetValue(row.FKColumn, false, out property)) 967 { 968 if (!property.Nullable) 969 { 970 return false; 971 } 972 } 973 else 974 { 975 Debug.Fail("Why didn't we find the column?"); 976 return false; 977 } 978 } 979 return true; 980 } 981 CreateAssociationEnd(LoadMethodSessionState session, EntityType type, RelationshipMultiplicity multiplicity, UniqueIdentifierService usedEndNames, OperationAction deleteAction )982 private AssociationEndMember CreateAssociationEnd(LoadMethodSessionState session, 983 EntityType type, 984 RelationshipMultiplicity multiplicity, 985 UniqueIdentifierService usedEndNames, 986 OperationAction deleteAction 987 ) 988 { 989 string role = usedEndNames.AdjustIdentifier(type.Name); 990 RefType refType = type.GetReferenceType(); 991 AssociationEndMember end = new AssociationEndMember(role, refType, multiplicity); 992 end.DeleteBehavior = deleteAction; 993 session.RelationshipEndTypeLookup.Add(end, type); 994 return end; 995 } 996 CreateReferentialConstraint(LoadMethodSessionState session, AssociationType association, AssociationEndMember pkEnd, AssociationEndMember fkEnd, List<RelationshipDetailsRow> columns, List<EdmSchemaError> errors)997 private bool CreateReferentialConstraint(LoadMethodSessionState session, 998 AssociationType association, 999 AssociationEndMember pkEnd, 1000 AssociationEndMember fkEnd, 1001 List<RelationshipDetailsRow> columns, 1002 List<EdmSchemaError> errors) 1003 { 1004 EdmProperty[] fromProperties = new EdmProperty[columns.Count]; 1005 EdmProperty[] toProperties = new EdmProperty[columns.Count]; 1006 EntityType pkEntityType = session.RelationshipEndTypeLookup[pkEnd]; 1007 EntityType fkEntityType = session.RelationshipEndTypeLookup[fkEnd]; 1008 for (int index = 0; index < columns.Count; index++) 1009 { 1010 EdmProperty property; 1011 1012 if(!pkEntityType.Properties.TryGetValue(columns[index].PKColumn, false, out property)) 1013 { 1014 errors.Add( 1015 new EdmSchemaError( 1016 Strings.AssociationMissingKeyColumn( 1017 pkEntityType.Name, 1018 fkEntityType.Name, 1019 pkEntityType.Name + "." + columns[index].PKColumn), 1020 (int)ModelBuilderErrorCode.AssociationMissingKeyColumn, 1021 EdmSchemaErrorSeverity.Warning)); 1022 return false; 1023 } 1024 fromProperties[index] = property; 1025 1026 if(!fkEntityType.Properties.TryGetValue(columns[index].FKColumn, false, out property)) 1027 { 1028 errors.Add( 1029 new EdmSchemaError( 1030 Strings.AssociationMissingKeyColumn( 1031 pkEntityType.Name, 1032 fkEntityType.Name, 1033 fkEntityType.Name + "." + columns[index].FKColumn), 1034 (int)ModelBuilderErrorCode.AssociationMissingKeyColumn, 1035 EdmSchemaErrorSeverity.Warning)); 1036 return false; 1037 } 1038 toProperties[index] = property; 1039 } 1040 1041 ReferentialConstraint constraint = new ReferentialConstraint(pkEnd, 1042 fkEnd, 1043 fromProperties, 1044 toProperties); 1045 1046 association.AddReferentialConstraint(constraint); 1047 return true; 1048 } 1049 IsFkPartiallyContainedInPK(AssociationType association, out string errorMessage)1050 static internal bool IsFkPartiallyContainedInPK(AssociationType association, out string errorMessage) 1051 { 1052 ReferentialConstraint constraint = association.ReferentialConstraints[0]; 1053 EntityType toType = (EntityType)constraint.ToProperties[0].DeclaringType; 1054 1055 bool toPropertiesAreFullyContainedInPk = true; 1056 bool toPropertiesContainedAtLeastOnePK = false; 1057 1058 foreach (EdmProperty edmProperty in constraint.ToProperties) 1059 { 1060 // check if there is at least one to property is not primary key 1061 toPropertiesAreFullyContainedInPk &= toType.KeyMembers.Contains(edmProperty); 1062 // check if there is one to property is primary key 1063 toPropertiesContainedAtLeastOnePK |= toType.KeyMembers.Contains(edmProperty); 1064 } 1065 if (!toPropertiesAreFullyContainedInPk && toPropertiesContainedAtLeastOnePK) 1066 { 1067 string foreignKeys = MetadataUtil.MembersToCommaSeparatedString((System.Collections.IEnumerable)constraint.ToProperties); 1068 string primaryKeys = MetadataUtil.MembersToCommaSeparatedString((System.Collections.IEnumerable)toType.KeyMembers); 1069 errorMessage = Strings.UnsupportedForeignKeyPattern(association.Name, foreignKeys, primaryKeys, toType.Name); 1070 return true; 1071 } 1072 errorMessage = ""; 1073 return false; 1074 } 1075 CreateViewEntityTypes(LoadMethodSessionState session)1076 private void CreateViewEntityTypes(LoadMethodSessionState session) 1077 { 1078 CreateTableTypes(session, _loader.LoadViewDetails(session.Filters), CreateEntityType, DbObjectType.View); 1079 } 1080 CreateTableEntityTypes(LoadMethodSessionState session)1081 private void CreateTableEntityTypes(LoadMethodSessionState session) 1082 { 1083 CreateTableTypes(session, _loader.LoadTableDetails(session.Filters), CreateEntityType, DbObjectType.Table); 1084 } 1085 CreateTvfReturnRowTypes(LoadMethodSessionState session)1086 private void CreateTvfReturnRowTypes(LoadMethodSessionState session) 1087 { 1088 CreateTableTypes(session, _loader.LoadFunctionReturnTableDetails(session.Filters), CreateTvfReturnRowType, DbObjectType.Function); 1089 } 1090 CreateTableTypes( LoadMethodSessionState session, IEnumerable<DataRow> tableDetailsRows, Action< LoadMethodSessionState , IList<TableDetailsRow> , ICollection<string> , DbObjectType , List<EdmSchemaError> > createType, DbObjectType objectType)1091 private void CreateTableTypes( 1092 LoadMethodSessionState session, 1093 IEnumerable<DataRow> tableDetailsRows, 1094 Action< 1095 LoadMethodSessionState/*session*/, 1096 IList<TableDetailsRow>/*columns*/, 1097 ICollection<string>/*primaryKeys*/, 1098 DbObjectType/*objectType*/, 1099 List<EdmSchemaError>/*errors*/> createType, 1100 DbObjectType objectType) 1101 { 1102 DbObjectKey currentKey = new DbObjectKey(); 1103 List<TableDetailsRow> singleTableColumns = new List<TableDetailsRow>(); 1104 List<string> primaryKeys = new List<string>(); 1105 foreach (TableDetailsRow row in tableDetailsRows) 1106 { 1107 DbObjectKey rowKey = row.CreateDbObjectKey(objectType); 1108 if (rowKey != currentKey) 1109 { 1110 if (singleTableColumns.Count != 0) 1111 { 1112 createType( 1113 session, 1114 singleTableColumns, 1115 primaryKeys, 1116 objectType, 1117 null); 1118 1119 singleTableColumns.Clear(); 1120 primaryKeys.Clear(); 1121 } 1122 currentKey = rowKey; 1123 } 1124 1125 singleTableColumns.Add(row); 1126 if (row.IsPrimaryKey) 1127 { 1128 primaryKeys.Add(row.ColumnName); 1129 } 1130 } 1131 1132 // pick up the last one 1133 if (singleTableColumns.Count != 0) 1134 { 1135 createType( 1136 session, 1137 singleTableColumns, 1138 primaryKeys, 1139 objectType, 1140 null); 1141 1142 } 1143 } 1144 CreateEntityType( LoadMethodSessionState session, IList<TableDetailsRow> columns, ICollection<string> primaryKeys, DbObjectType objectType, List<EdmSchemaError> errors)1145 private void CreateEntityType( 1146 LoadMethodSessionState session, 1147 IList<TableDetailsRow> columns, 1148 ICollection<string> primaryKeys, 1149 DbObjectType objectType, 1150 List<EdmSchemaError> errors) 1151 { 1152 Debug.Assert(columns.Count != 0, "Trying to create an EntityType with 0 properties"); 1153 Debug.Assert(primaryKeys != null, "primaryKeys != null"); 1154 1155 DbObjectKey tableKey = columns[0].CreateDbObjectKey(objectType); 1156 if (errors == null) 1157 { 1158 errors = new List<EdmSchemaError>(); 1159 } 1160 1161 // 1162 // Handle Tables without explicit declaration of keys 1163 // 1164 EntityCreationStatus status = EntityCreationStatus.Normal; 1165 if (primaryKeys.Count == 0) 1166 { 1167 List<string> pKeys = new List<string>(columns.Count); 1168 session.AddTableWithoutKey(tableKey); 1169 if (InferKeyColumns(session, columns, pKeys, tableKey, ref primaryKeys)) 1170 { 1171 errors.Add(new EdmSchemaError( 1172 Strings.NoPrimaryKeyDefined(tableKey), 1173 (int)ModelBuilderErrorCode.NoPrimaryKeyDefined, 1174 EdmSchemaErrorSeverity.Warning)); 1175 status = EntityCreationStatus.ReadOnly; 1176 } 1177 else 1178 { 1179 errors.Add(new EdmSchemaError( 1180 Strings.CannotCreateEntityWithNoPrimaryKeyDefined(tableKey), 1181 (int)ModelBuilderErrorCode.CannotCreateEntityWithoutPrimaryKey, 1182 EdmSchemaErrorSeverity.Warning)); 1183 status = EntityCreationStatus.Invalid; 1184 } 1185 } 1186 1187 Debug.Assert(primaryKeys == null || primaryKeys.Count > 0,"There must be at least one key columns at this point in time"); 1188 1189 IList<string> excludedColumns; 1190 var properties = CreateEdmProperties(session, columns, tableKey, errors, out excludedColumns); 1191 1192 var excludedKeyColumns = (primaryKeys != null ? primaryKeys.Intersect(excludedColumns) : new string[0]).ToArray(); 1193 1194 if (primaryKeys != null && excludedKeyColumns.Length == 0) 1195 { 1196 foreach (EdmMember pkColumn in properties.Where(p => primaryKeys.Contains(p.Name))) 1197 { 1198 if (!MetadataUtil.IsValidKeyType(_targetEntityFrameworkVersion, pkColumn.TypeUsage.EdmType)) 1199 { 1200 // make it a read-only table by calling this method recursively with no keys 1201 errors = new List<EdmSchemaError>(); 1202 var tableColumn = columns.Where(c => c.ColumnName == pkColumn.Name).Single(); 1203 errors.Add(new EdmSchemaError(Strings.InvalidTypeForPrimaryKey(tableColumn.GetMostQualifiedTableName(), 1204 tableColumn.ColumnName, 1205 tableColumn.DataType), 1206 (int)ModelBuilderErrorCode.InvalidKeyTypeFound, 1207 EdmSchemaErrorSeverity.Warning)); 1208 1209 string[] keyColumns = new string[0]; 1210 CreateEntityType(session, columns, keyColumns, objectType, errors); 1211 return; 1212 } 1213 } 1214 } 1215 1216 if (excludedKeyColumns.Length > 0) 1217 { 1218 // see if we have any keys left 1219 if (primaryKeys != null && excludedKeyColumns.Length < primaryKeys.Count) 1220 { 1221 primaryKeys = primaryKeys.Except(excludedKeyColumns).ToList(); 1222 status = EntityCreationStatus.ReadOnly; 1223 } 1224 else 1225 { 1226 primaryKeys = null; 1227 status = EntityCreationStatus.Invalid; 1228 } 1229 1230 foreach (string columnName in excludedKeyColumns) 1231 { 1232 if (status == EntityCreationStatus.ReadOnly) 1233 { 1234 errors.Add(new EdmSchemaError( 1235 Strings.ExcludedColumnWasAKeyColumnEntityIsReadOnly(columnName, columns[0].GetMostQualifiedTableName()), 1236 (int)ModelBuilderErrorCode.ExcludedColumnWasAKeyColumn, 1237 EdmSchemaErrorSeverity.Warning)); 1238 } 1239 else 1240 { 1241 Debug.Assert(status == EntityCreationStatus.Invalid, "Did we change some code above to make it possible to be something different?"); 1242 errors.Add(new EdmSchemaError( 1243 Strings.ExcludedColumnWasAKeyColumnEntityIsInvalid(columnName, columns[0].GetMostQualifiedTableName()), 1244 (int)ModelBuilderErrorCode.ExcludedColumnWasAKeyColumn, 1245 EdmSchemaErrorSeverity.Warning)); 1246 } 1247 } 1248 } 1249 1250 string typeName = session.UsedTypeNames.AdjustIdentifier(columns[0].TableName); 1251 var entityType = new EntityType(typeName, _namespaceName, DataSpace.SSpace, primaryKeys, properties); 1252 entityType.SetReadOnly(); 1253 1254 switch (status) 1255 { 1256 case EntityCreationStatus.Normal: 1257 session.AddEntity(tableKey, entityType); 1258 break; 1259 case EntityCreationStatus.ReadOnly: 1260 session.AddEntity(tableKey, entityType); 1261 session.ReadOnlyEntities.Add(entityType); 1262 break; 1263 default: 1264 Debug.Assert(status == EntityCreationStatus.Invalid, "did you add a new value?"); 1265 session.InvalidTypes.Add(entityType); 1266 break; 1267 } 1268 1269 session.AddErrorsForType(entityType, errors); 1270 } 1271 CreateTvfReturnRowType( LoadMethodSessionState session, IList<TableDetailsRow> columns, ICollection<string> primaryKeys, DbObjectType objectType, List<EdmSchemaError> errors)1272 private void CreateTvfReturnRowType( 1273 LoadMethodSessionState session, 1274 IList<TableDetailsRow> columns, 1275 ICollection<string> primaryKeys, 1276 DbObjectType objectType, 1277 List<EdmSchemaError> errors) 1278 { 1279 Debug.Assert(columns.Count != 0, "Trying to create a RowType with 0 properties"); 1280 Debug.Assert(primaryKeys != null, "primaryKeys != null"); 1281 1282 DbObjectKey tableKey = columns[0].CreateDbObjectKey(objectType); 1283 if (errors == null) 1284 { 1285 errors = new List<EdmSchemaError>(); 1286 } 1287 1288 IList<string> excludedColumns; 1289 var properties = CreateEdmProperties(session, columns, tableKey, errors, out excludedColumns); 1290 1291 var rowType = new RowType(properties); 1292 rowType.SetReadOnly(); 1293 session.AddTvfReturnType(tableKey, rowType); 1294 if (rowType.Properties.Count == 0) 1295 { 1296 session.InvalidTypes.Add(rowType); 1297 } 1298 session.AddErrorsForType(rowType, errors); 1299 } 1300 CreateEdmProperties( LoadMethodSessionState session, IList<TableDetailsRow> columns, DbObjectKey tableKey, List<EdmSchemaError> errors, out IList<string> excludedColumns)1301 private IList<EdmProperty> CreateEdmProperties( 1302 LoadMethodSessionState session, 1303 IList<TableDetailsRow> columns, 1304 DbObjectKey tableKey, 1305 List<EdmSchemaError> errors, 1306 out IList<string> excludedColumns) 1307 { 1308 Debug.Assert(columns.Count != 0, "columns.Count != 0"); 1309 Debug.Assert(errors != null, "errors != null"); 1310 1311 var members = new List<EdmProperty>(); 1312 excludedColumns = new List<string>(); 1313 foreach (TableDetailsRow row in columns) 1314 { 1315 PrimitiveType primitiveType; 1316 bool excludedForTarget = false; 1317 if (row.IsDataTypeNull() || !session.TryGetStorePrimitiveType(row.DataType, out primitiveType, out excludedForTarget)) 1318 { 1319 string message; 1320 if (!row.IsDataTypeNull()) 1321 { 1322 message = excludedForTarget ? 1323 Strings.UnsupportedDataTypeForTarget(row.DataType, row.GetMostQualifiedTableName(), row.ColumnName) : 1324 Strings.UnsupportedDataType(row.DataType, row.GetMostQualifiedTableName(), row.ColumnName); 1325 } 1326 else 1327 { 1328 message = Strings.UnsupportedDataTypeUnknownType(row.ColumnName, row.GetMostQualifiedTableName()); 1329 } 1330 1331 errors.Add(new EdmSchemaError(message, (int)ModelBuilderErrorCode.UnsupportedType, EdmSchemaErrorSeverity.Warning)); 1332 excludedColumns.Add(row.ColumnName); 1333 1334 continue; 1335 } 1336 1337 Dictionary<string, Facet> facets = primitiveType.GetAssociatedFacetDescriptions().ToDictionary(fd => fd.FacetName, fd => fd.DefaultValueFacet); 1338 facets[DbProviderManifest.NullableFacetName] = Facet.Create(facets[DbProviderManifest.NullableFacetName].Description, row.IsNullable); 1339 1340 if (primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.Decimal) 1341 { 1342 Facet precision; 1343 if (facets.TryGetValue(DbProviderManifest.PrecisionFacetName, out precision)) 1344 { 1345 if (!row.IsPrecisionNull() && !precision.Description.IsConstant) 1346 { 1347 if (row.Precision < precision.Description.MinValue || row.Precision > precision.Description.MaxValue) 1348 { 1349 DbObjectKey key = row.CreateDbObjectKey(tableKey.ObjectType); 1350 errors.Add(new EdmSchemaError( 1351 Strings.ColumnFacetValueOutOfRange( 1352 DbProviderManifest.PrecisionFacetName, 1353 row.Precision, 1354 precision.Description.MinValue, 1355 precision.Description.MaxValue, 1356 row.ColumnName, 1357 key), 1358 (int)ModelBuilderErrorCode.FacetValueOutOfRange, 1359 EdmSchemaErrorSeverity.Warning)); 1360 excludedColumns.Add(row.ColumnName); 1361 continue; 1362 } 1363 facets[precision.Name] = Facet.Create(precision.Description, (byte)row.Precision); 1364 } 1365 } 1366 Facet scale; 1367 if (facets.TryGetValue(DbProviderManifest.ScaleFacetName, out scale)) 1368 { 1369 if (!row.IsScaleNull() && !scale.Description.IsConstant) 1370 { 1371 if (row.Scale < scale.Description.MinValue || row.Scale > scale.Description.MaxValue) 1372 { 1373 DbObjectKey key = row.CreateDbObjectKey(tableKey.ObjectType); 1374 errors.Add(new EdmSchemaError( 1375 Strings.ColumnFacetValueOutOfRange( 1376 DbProviderManifest.ScaleFacetName, 1377 row.Scale, 1378 scale.Description.MinValue, 1379 scale.Description.MaxValue, 1380 row.ColumnName, 1381 key), 1382 (int)ModelBuilderErrorCode.FacetValueOutOfRange, 1383 EdmSchemaErrorSeverity.Warning)); 1384 excludedColumns.Add(row.ColumnName); 1385 continue; 1386 } 1387 facets[scale.Name] = Facet.Create(scale.Description, (byte)row.Scale); 1388 } 1389 } 1390 } 1391 else if (primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.DateTime || 1392 primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.Time || 1393 primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.DateTimeOffset) 1394 { 1395 Facet datetimePrecision; 1396 if (facets.TryGetValue(DbProviderManifest.PrecisionFacetName, out datetimePrecision)) 1397 { 1398 if (!row.IsDateTimePrecisionNull() && !datetimePrecision.Description.IsConstant) 1399 { 1400 if (row.DateTimePrecision < datetimePrecision.Description.MinValue || row.DateTimePrecision > datetimePrecision.Description.MaxValue) 1401 { 1402 DbObjectKey key = row.CreateDbObjectKey(tableKey.ObjectType); 1403 errors.Add(new EdmSchemaError( 1404 Strings.ColumnFacetValueOutOfRange( 1405 DbProviderManifest.PrecisionFacetName, 1406 row.DateTimePrecision, 1407 datetimePrecision.Description.MinValue, 1408 datetimePrecision.Description.MaxValue, 1409 row.ColumnName, 1410 key), 1411 (int)ModelBuilderErrorCode.FacetValueOutOfRange, 1412 EdmSchemaErrorSeverity.Warning)); 1413 excludedColumns.Add(row.ColumnName); 1414 continue; 1415 } 1416 facets[datetimePrecision.Name] = Facet.Create(datetimePrecision.Description, (byte)row.DateTimePrecision); 1417 } 1418 } 1419 } 1420 else if (primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.String || 1421 primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.Binary) 1422 { 1423 Facet maxLength; 1424 if (facets.TryGetValue(DbProviderManifest.MaxLengthFacetName, out maxLength)) 1425 { 1426 if (!row.IsMaximumLengthNull() && !maxLength.Description.IsConstant) 1427 { 1428 if (row.MaximumLength < maxLength.Description.MinValue || row.MaximumLength > maxLength.Description.MaxValue) 1429 { 1430 DbObjectKey key = row.CreateDbObjectKey(tableKey.ObjectType); 1431 errors.Add(new EdmSchemaError( 1432 Strings.ColumnFacetValueOutOfRange( 1433 DbProviderManifest.MaxLengthFacetName, 1434 row.MaximumLength, 1435 maxLength.Description.MinValue, 1436 maxLength.Description.MaxValue, 1437 row.ColumnName, 1438 key), 1439 (int)ModelBuilderErrorCode.FacetValueOutOfRange, 1440 EdmSchemaErrorSeverity.Warning)); 1441 excludedColumns.Add(row.ColumnName); 1442 continue; 1443 } 1444 facets[maxLength.Name] = Facet.Create(maxLength.Description, row.MaximumLength); 1445 } 1446 } 1447 } 1448 1449 if (!row.IsIsIdentityNull() && row.IsIdentity) 1450 { 1451 Facet facet = Facet.Create(System.Data.Metadata.Edm.Converter.StoreGeneratedPatternFacet, StoreGeneratedPattern.Identity); 1452 facets.Add(facet.Name, facet); 1453 } 1454 else if (!row.IsIsServerGeneratedNull() && row.IsServerGenerated) 1455 { 1456 Facet facet = Facet.Create(System.Data.Metadata.Edm.Converter.StoreGeneratedPatternFacet, StoreGeneratedPattern.Computed); 1457 facets.Add(facet.Name, facet); 1458 } 1459 1460 members.Add(new EdmProperty(row.ColumnName, TypeUsage.Create(primitiveType, facets.Values))); 1461 } 1462 1463 return members; 1464 } 1465 InferKeyColumns(LoadMethodSessionState session, IList<TableDetailsRow> columns, List<string> pKeys, DbObjectKey tableKey, ref ICollection<string> primaryKeys)1466 private bool InferKeyColumns(LoadMethodSessionState session, IList<TableDetailsRow> columns, List<string> pKeys, DbObjectKey tableKey, ref ICollection<string> primaryKeys) 1467 { 1468 for (int i = 0; i < columns.Count; i++) 1469 { 1470 if (!columns[i].IsNullable) 1471 { 1472 PrimitiveType primitiveType; 1473 bool _; 1474 if (session.TryGetStorePrimitiveType(columns[i].DataType, out primitiveType, out _) && 1475 MetadataUtil.IsValidKeyType(_targetEntityFrameworkVersion, primitiveType)) 1476 { 1477 pKeys.Add(columns[i].ColumnName); 1478 } 1479 } 1480 } 1481 1482 // if there are valid key column candidates, make them the new key columns 1483 if (pKeys.Count > 0) 1484 { 1485 primaryKeys = pKeys; 1486 } 1487 else 1488 { 1489 primaryKeys = null; 1490 } 1491 1492 return primaryKeys != null; 1493 } 1494 1495 /// <summary> 1496 /// Populates DefiningQuery attribute of RO view entities 1497 /// </summary> 1498 /// <param name="viewEntitySets"></param> 1499 /// <param name="entityContainer"></param> 1500 /// <param name="session"></param> FixupKeylessEntitySets(EntityContainer entityContainer, LoadMethodSessionState session)1501 private void FixupKeylessEntitySets(EntityContainer entityContainer, LoadMethodSessionState session) 1502 { 1503 // if there are views to process 1504 if (session.ReadOnlyEntities.Count > 0) 1505 { 1506 // 1507 // create 'bogus' metadataworkspace 1508 // 1509 MetadataWorkspace metadataWorkspace = CreateMetadataWorkspace(entityContainer, session); 1510 1511 if (null == metadataWorkspace) 1512 { 1513 // failed to create bogus metadataworkspace 1514 return; 1515 } 1516 1517 // 1518 // For all tables/views that we could infer valid keys, update DefiningQuery with 1519 // provider specific ReadOnly view SQL 1520 // 1521 foreach (EntityType entityType in session.ReadOnlyEntities) 1522 { 1523 EntitySet entitySet = session.EntityTypeToSet[entityType]; 1524 DbObjectKey key = session.GetKey(entityType); 1525 1526 // add properties that make it possible for the designer to track back these 1527 // types to their source db objects 1528 List<MetadataProperty> properties = new List<MetadataProperty>(); 1529 if (key.Schema != null) 1530 { 1531 properties.Add(System.Data.EntityModel.SchemaObjectModel.SchemaElement.CreateMetadataPropertyFromOtherNamespaceXmlArtifact(DesignXmlConstants.EntityStoreSchemaGeneratorNamespace, DesignXmlConstants.EntityStoreSchemaGeneratorSchemaAttributeName, key.Schema)); 1532 } 1533 properties.Add(System.Data.EntityModel.SchemaObjectModel.SchemaElement.CreateMetadataPropertyFromOtherNamespaceXmlArtifact(DesignXmlConstants.EntityStoreSchemaGeneratorNamespace, DesignXmlConstants.EntityStoreSchemaGeneratorNameAttributeName, key.TableName)); 1534 entitySet.AddMetadataProperties(properties); 1535 1536 FixupViewEntitySetDefiningQuery(entitySet, metadataWorkspace); 1537 } 1538 } 1539 } 1540 1541 /// <summary> 1542 /// Creates 'transient' metadataworkspace based on store schema (EntityContainer) and trivial C-S mapping 1543 /// </summary> 1544 /// <param name="entityContainer"></param> 1545 /// <param name="session"></param> 1546 /// <returns></returns> CreateMetadataWorkspace(EntityContainer entityContainer, LoadMethodSessionState session)1547 private MetadataWorkspace CreateMetadataWorkspace(EntityContainer entityContainer, LoadMethodSessionState session) 1548 { 1549 MetadataWorkspace metadataWorkspace = new MetadataWorkspace(); 1550 1551 EntityModelSchemaGenerator modelGen = new EntityModelSchemaGenerator(entityContainer); 1552 modelGen.GenerateForeignKeyProperties = this.GenerateForeignKeyProperties; 1553 1554 IEnumerable<EdmSchemaError> errors = modelGen.GenerateMetadata(); 1555 1556 if (EntityStoreSchemaGenerator.HasErrorSeverityErrors(errors)) 1557 { 1558 // this is a 'transient' metadataworkspace 1559 // no errors from this metadataworkspace should be shown to the user 1560 return null; 1561 } 1562 1563 // register edmitemcollection 1564 metadataWorkspace.RegisterItemCollection(modelGen.EdmItemCollection); 1565 1566 // register StoreItemCollection 1567 metadataWorkspace.RegisterItemCollection(session.ItemCollection); 1568 1569 // register mapping 1570 using (MemoryStream memStream = new MemoryStream()) 1571 { 1572 using (XmlWriter xmlWriter = XmlWriter.Create(memStream)) 1573 { 1574 modelGen.WriteStorageMapping(xmlWriter); 1575 xmlWriter.Close(); 1576 } 1577 1578 memStream.Seek(0, SeekOrigin.Begin); 1579 1580 using (XmlReader xmlReader = XmlReader.Create(memStream)) 1581 { 1582 List<XmlReader> xmlReaders = new List<XmlReader>(); 1583 xmlReaders.Add(xmlReader); 1584 metadataWorkspace.RegisterItemCollection(new StorageMappingItemCollection(modelGen.EdmItemCollection, 1585 session.ItemCollection, 1586 xmlReaders)); 1587 } 1588 } 1589 1590 return metadataWorkspace; 1591 } 1592 1593 /// <summary> 1594 /// Generates provider specific, read only SQL and updates entitySet DefiningQuery 1595 /// </summary> 1596 /// <param name="entitySet"></param> 1597 /// <param name="metadataWorkspace"></param> FixupViewEntitySetDefiningQuery(EntitySet entitySet, MetadataWorkspace metadataWorkspace)1598 private void FixupViewEntitySetDefiningQuery(EntitySet entitySet, MetadataWorkspace metadataWorkspace) 1599 { 1600 DbExpressionBinding inputBinding = DbExpressionBuilder.BindAs(DbExpressionBuilder.Scan(entitySet), entitySet.Name); 1601 List<KeyValuePair<string, DbExpression>> projectList = new List<KeyValuePair<string, DbExpression>>(entitySet.ElementType.Members.Count); 1602 foreach (EdmMember member in entitySet.ElementType.Members) 1603 { 1604 Debug.Assert(member.BuiltInTypeKind == BuiltInTypeKind.EdmProperty, "Every member must be a edmproperty"); 1605 EdmProperty propertyInfo = (EdmProperty)member; 1606 projectList.Add(new KeyValuePair<string, DbExpression>(member.Name, 1607 DbExpressionBuilder.Property(inputBinding.Variable, propertyInfo))); 1608 } 1609 DbExpression query = inputBinding.Project(DbExpressionBuilder.NewRow(projectList)); 1610 DbQueryCommandTree dbCommandTree = new DbQueryCommandTree(metadataWorkspace, DataSpace.SSpace, query); 1611 1612 // 1613 // get provider SQL and set entitySet DefiningQuery 1614 // 1615 entitySet.DefiningQuery = (DbProviderServices.GetProviderServices(_loader.EntityConnection.StoreProviderFactory) 1616 .CreateCommandDefinition(dbCommandTree)) 1617 .CreateCommand().CommandText; 1618 1619 Debug.Assert(!String.IsNullOrEmpty(entitySet.DefiningQuery), "DefiningQuery must not be null or empty"); 1620 } 1621 } 1622 } 1623