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