|
//---------------------------------------------------------------------
// <copyright file="EntityClassGenerator.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Data;
using System.Data.EntityModel.SchemaObjectModel;
using System.Data.Metadata.Edm;
using System.Data.EntityModel;
using System.Data.Entity.Design.Common;
using System.IO;
using System.Xml;
using System.Data.Entity.Design.SsdlGenerator;
using Microsoft.Build.Utilities;
using System.Runtime.Versioning;
namespace System.Data.Entity.Design
{
/// <summary>
/// Event handler for the OnTypeGenerated event
/// </summary>
/// <param name="sender">The source of the event</param>
/// <param name="e">The event args</param>
public delegate void TypeGeneratedEventHandler(object sender, TypeGeneratedEventArgs e);
/// <summary>
/// Event handler for the OnPropertyGenerated event
/// </summary>
/// <param name="sender">The source of the event</param>
/// <param name="e">The event args</param>
public delegate void PropertyGeneratedEventHandler(object sender, PropertyGeneratedEventArgs e);
/// <summary>
/// Summary description for CodeGenerator.
/// </summary>
public sealed class EntityClassGenerator
{
#region Instance Fields
LanguageOption _languageOption = LanguageOption.GenerateCSharpCode;
EdmToObjectNamespaceMap _edmToObjectNamespaceMap = new EdmToObjectNamespaceMap();
#endregion
#region Events
/// <summary>
/// The event that is raised when a type is generated
/// </summary>
public event TypeGeneratedEventHandler OnTypeGenerated;
/// <summary>
/// The event that is raised when a property is generated
/// </summary>
public event PropertyGeneratedEventHandler OnPropertyGenerated;
#endregion
#region Public Methods
/// <summary>
///
/// </summary>
public EntityClassGenerator()
{
}
/// <summary>
///
/// </summary>
public EntityClassGenerator(LanguageOption languageOption)
{
_languageOption = EDesignUtil.CheckLanguageOptionArgument(languageOption, "languageOption");
}
/// <summary>
/// Gets and Sets the Language to use for code generation.
/// </summary>
public LanguageOption LanguageOption
{
get { return _languageOption; }
set { _languageOption = EDesignUtil.CheckLanguageOptionArgument(value, "value"); }
}
/// <summary>
/// Gets the map entries use to customize the namespace of .net types that are generated
/// and referenced by the generated code
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
public EdmToObjectNamespaceMap EdmToObjectNamespaceMap
{
get { return _edmToObjectNamespaceMap; }
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
public IList<EdmSchemaError> GenerateCode(XmlReader sourceEdmSchema, TextWriter target)
{
EDesignUtil.CheckArgumentNull(sourceEdmSchema, "sourceEdmSchema");
EDesignUtil.CheckArgumentNull(target, "target");
return GenerateCode(sourceEdmSchema, target, new XmlReader[] { });
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
[ResourceExposure(ResourceScope.None)] //No resource is exposed since we pass in null as the target paath.
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] //For GenerateCodeCommon method call. Since we use null as the path, we have not changed the scope of the resource.
public IList<EdmSchemaError> GenerateCode(XmlReader sourceEdmSchema, TextWriter target, IEnumerable<XmlReader> additionalEdmSchemas)
{
EDesignUtil.CheckArgumentNull(sourceEdmSchema, "sourceEdmSchema");
EDesignUtil.CheckArgumentNull(additionalEdmSchemas, "additionalEdmSchemas");
EDesignUtil.CheckArgumentNull(target, "target");
List<EdmSchemaError> errors = new List<EdmSchemaError>();
try
{
MetadataArtifactLoader sourceLoader = new MetadataArtifactLoaderXmlReaderWrapper(sourceEdmSchema);
List<MetadataArtifactLoader> loaders = new List<MetadataArtifactLoader>();
loaders.Add(sourceLoader);
int index = 0;
foreach (XmlReader additionalEdmSchema in additionalEdmSchemas)
{
if (additionalEdmSchema == null)
{
throw EDesignUtil.Argument(Strings.NullAdditionalSchema("additionalEdmSchema", index));
}
try
{
MetadataArtifactLoader loader = new MetadataArtifactLoaderXmlReaderWrapper(additionalEdmSchema);
Debug.Assert(loader != null, "when is the loader ever null?");
loaders.Add(loader);
}
catch (Exception e)
{
if (MetadataUtil.IsCatchableExceptionType(e))
{
errors.Add(new EdmSchemaError(e.Message,
(int)ModelBuilderErrorCode.CodeGenAdditionalEdmSchemaIsInvalid,
EdmSchemaErrorSeverity.Error));
}
else
{
throw;
}
}
index++;
}
ThrowOnAnyNonWarningErrors(errors);
GenerateCodeCommon(sourceLoader,
loaders,
new LazyTextWriterCreator(target),
null, // source path
null, // target file path
false, // dispose readers?
errors);
}
catch (TerminalErrorException)
{
// do nothing
// just a place to jump when errors are detected
}
return errors;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
[ResourceExposure(ResourceScope.Machine)] //Exposes the sourceEdmSchemaFilePath which is a Machine resource
[ResourceConsumption(ResourceScope.Machine)] //For GenerateCode method call. But the path is not created in this method.
public IList<EdmSchemaError> GenerateCode(string sourceEdmSchemaFilePath, string targetFilePath)
{
return GenerateCode(sourceEdmSchemaFilePath, targetFilePath, new string[] { });
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
[ResourceExposure(ResourceScope.Machine)] //Exposes the sourceEdmSchemaFilePath which is a Machine resource
[ResourceConsumption(ResourceScope.Machine)] //For MetadataArtifactLoader.Create method call. But the path is not created in this method.
public IList<EdmSchemaError> GenerateCode(string sourceEdmSchemaFilePath, string targetPath, IEnumerable<string> additionalEdmSchemaFilePaths)
{
EDesignUtil.CheckStringArgument(sourceEdmSchemaFilePath, "sourceEdmSchemaFilePath");
EDesignUtil.CheckArgumentNull(additionalEdmSchemaFilePaths, "additionalEdmSchemaFilePaths");
EDesignUtil.CheckStringArgument(targetPath, "targetPath");
List<EdmSchemaError> errors = new List<EdmSchemaError>();
try
{
// create a loader for the source
HashSet<string> uriRegistry = new HashSet<string>();
MetadataArtifactLoader sourceLoader;
try
{
sourceLoader = MetadataArtifactLoader.Create(sourceEdmSchemaFilePath, MetadataArtifactLoader.ExtensionCheck.Specific,
XmlConstants.CSpaceSchemaExtension, uriRegistry);
}
catch (MetadataException e)
{
errors.Add(CreateErrorForException(ModelBuilderErrorCode.CodeGenSourceFilePathIsInvalid, e, sourceEdmSchemaFilePath));
return errors;
}
if (sourceLoader.IsComposite)
{
throw new ArgumentException(Strings.CodeGenSourceFilePathIsNotAFile, "sourceEdmSchemaPath");
}
// create loaders for all the additional schemas
List<MetadataArtifactLoader> loaders = new List<MetadataArtifactLoader>();
loaders.Add(sourceLoader);
int index = 0;
foreach (string additionalSchemaFilePath in additionalEdmSchemaFilePaths)
{
if (additionalSchemaFilePath == null)
{
throw EDesignUtil.Argument(Strings.NullAdditionalSchema("additionalEdmSchemaFilePaths", index));
}
try
{
MetadataArtifactLoader loader = MetadataArtifactLoader.Create(additionalSchemaFilePath,
MetadataArtifactLoader.ExtensionCheck.Specific,
XmlConstants.CSpaceSchemaExtension, uriRegistry);
Debug.Assert(loader != null, "when is the loader ever null?");
loaders.Add(loader);
}
catch (Exception e)
{
if(MetadataUtil.IsCatchableExceptionType(e))
{
errors.Add(CreateErrorForException(ModelBuilderErrorCode.CodeGenAdditionalEdmSchemaIsInvalid, e, additionalSchemaFilePath));
}
else
{
throw;
}
}
index++;
}
ThrowOnAnyNonWarningErrors(errors);
try
{
using (LazyTextWriterCreator target = new LazyTextWriterCreator(targetPath))
{
GenerateCodeCommon(sourceLoader, loaders, target, sourceEdmSchemaFilePath, targetPath,
true, // dispose readers
errors);
}
}
catch (System.IO.IOException ex)
{
errors.Add(CreateErrorForException(System.Data.EntityModel.SchemaObjectModel.ErrorCode.IOException, ex, targetPath));
return errors;
}
}
catch (TerminalErrorException)
{
// do nothing
// just a place to jump when errors are detected
}
return errors;
}
private void GenerateCodeCommon(MetadataArtifactLoader sourceLoader,
List<MetadataArtifactLoader> loaders,
LazyTextWriterCreator target,
string sourceEdmSchemaFilePath,
string targetFilePath,
bool closeReaders,
List<EdmSchemaError> errors)
{
MetadataArtifactLoaderComposite composite = new MetadataArtifactLoaderComposite(loaders);
// create the schema manager from the xml readers
Dictionary<MetadataArtifactLoader, XmlReader> readerSourceMap = new Dictionary<MetadataArtifactLoader, XmlReader>();
IList<Schema> schemas;
List<XmlReader> readers = composite.GetReaders(readerSourceMap);
try
{
IList<EdmSchemaError> schemaManagerErrors =
SchemaManager.ParseAndValidate(readers,
composite.GetPaths(),
SchemaDataModelOption.EntityDataModel,
EdmProviderManifest.Instance,
out schemas);
errors.AddRange(schemaManagerErrors);
}
finally
{
if (closeReaders)
{
MetadataUtil.DisposeXmlReaders(readers);
}
}
ThrowOnAnyNonWarningErrors(errors);
Debug.Assert(readerSourceMap.ContainsKey(sourceLoader), "the source loader didn't produce any of the xml readers...");
XmlReader sourceReader = readerSourceMap[sourceLoader];
// use the index of the "source" xml reader as the index of the "source" schema
Debug.Assert(readers.Contains(sourceReader), "the source reader is not in the list of readers");
int index = readers.IndexOf(sourceReader);
Debug.Assert(index >= 0, "couldn't find the source reader in the list of readers");
Debug.Assert(readers.Count == schemas.Count, "We have a different number of readers than schemas");
Schema sourceSchema = schemas[index];
Debug.Assert(sourceSchema != null, "sourceSchema is null");
// create the EdmItemCollection from the schemas
EdmItemCollection itemCollection = new EdmItemCollection(schemas);
if (EntityFrameworkVersionsUtil.ConvertToVersion(itemCollection.EdmVersion) >= EntityFrameworkVersions.Version2)
{
throw EDesignUtil.InvalidOperation(Strings.TargetEntityFrameworkVersionToNewForEntityClassGenerator);
}
// generate code
ClientApiGenerator generator = new ClientApiGenerator(sourceSchema, itemCollection, this, errors);
generator.GenerateCode(target, targetFilePath);
}
#endregion
#region Private Methods
private static EdmSchemaError CreateErrorForException(System.Data.EntityModel.SchemaObjectModel.ErrorCode errorCode, System.Exception exception, string sourceLocation)
{
Debug.Assert(exception != null);
Debug.Assert(sourceLocation != null);
return new EdmSchemaError(exception.Message, (int)errorCode, EdmSchemaErrorSeverity.Error, sourceLocation, 0, 0, exception);
}
internal static EdmSchemaError CreateErrorForException(ModelBuilderErrorCode errorCode, System.Exception exception, string sourceLocation)
{
Debug.Assert(exception != null);
Debug.Assert(sourceLocation != null);
return new EdmSchemaError(exception.Message, (int)errorCode, EdmSchemaErrorSeverity.Error, sourceLocation, 0, 0, exception);
}
internal static EdmSchemaError CreateErrorForException(ModelBuilderErrorCode errorCode, System.Exception exception)
{
Debug.Assert(exception != null);
return new EdmSchemaError(exception.Message, (int)errorCode, EdmSchemaErrorSeverity.Error, null, 0, 0, exception);
}
private void ThrowOnAnyNonWarningErrors(List<EdmSchemaError> errors)
{
foreach (EdmSchemaError error in errors)
{
if (error.Severity != EdmSchemaErrorSeverity.Warning)
{
throw new TerminalErrorException();
}
}
}
#endregion
#region Event Helpers
/// <summary>
/// Helper method that raises the TypeGenerated event
/// </summary>
/// <param name="eventArgs">The event arguments passed to the subscriber</param>
internal void RaiseTypeGeneratedEvent(TypeGeneratedEventArgs eventArgs)
{
if (this.OnTypeGenerated != null)
{
this.OnTypeGenerated(this, eventArgs);
}
}
/// <summary>
/// Helper method that raises the PropertyGenerated event
/// </summary>
/// <param name="eventArgs">The event arguments passed to the subscriber</param>
internal void RaisePropertyGeneratedEvent(PropertyGeneratedEventArgs eventArgs)
{
if (this.OnPropertyGenerated != null)
{
this.OnPropertyGenerated(this, eventArgs);
}
}
#endregion
}
}
|