File: System\Data\Entity\Design\EntityCodeGenerator.cs
Project: ndp\fx\src\DataEntityDesign\Design\System.Data.Entity.Design.csproj (System.Data.Entity.Design)
//---------------------------------------------------------------------
// <copyright file="EntityCodeGenerator.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
//#define ENABLE_TEMPLATE_DEBUGGING
 
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Data.Entity.Design.Common;
using System.Data.Entity.Design.SsdlGenerator;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
using System.Threading;
using System.Xml;
 
namespace System.Data.Entity.Design
{
    public class EntityCodeGenerator
    {
        private LanguageOption _languageOption = LanguageOption.GenerateCSharpCode;
        private EdmToObjectNamespaceMap _edmToObjectNamespaceMap = new EdmToObjectNamespaceMap();
 
        public EntityCodeGenerator(LanguageOption languageOption)
        {
            _languageOption = EDesignUtil.CheckLanguageOptionArgument(languageOption, "languageOption");
        }
 
        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; }
        }
 
        /// <summary>
        /// Creates a source code file that contains object layer code generated from the specified conceptual schema definition
        /// language (CSDL) file. The list of schema file paths is used to resolve any references contained in the CSDL file.
        /// Note that the targetEntityFrameworkVersion parameter uses internal EntityFramework version numbers as described
        /// in the <see cref="EntityFrameworkVersions"/> class.
        /// </summary>
        /// <param name="sourceEdmSchemaFilePath">The path of the CSDL file.</param>
        /// <param name="targetPath">The path of the file that contains the generated object layer code.</param>
        /// <param name="additionalEdmSchemaFilePaths">
        /// A list of schema file paths that can be used to resolve any references in the source schema (the CSDL file).
        /// If the source schema does not have any dependencies, pass in an empty list.
        /// </param>
        /// <param name="targetEntityFrameworkVersion">The internal Entity Framework version that is being targeted.</param>
        /// <returns>A list of <see cref="EdmSchemaError"/> objects that contains any generated errors.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
        [ResourceExposure(ResourceScope.Machine)] // for sourceEdmSchemaFilePath, targetPath and additionalEdmSchema which are machine resources
        [ResourceConsumption(ResourceScope.Machine)] // for InternalGenerateCode method call. But the path is not created in this method.
        public IList<EdmSchemaError> GenerateCode(string sourceEdmSchemaFilePath, string targetPath, IEnumerable<string> additionalEdmSchemaFilePaths, Version targetEntityFrameworkVersion)
        {
            EDesignUtil.CheckTargetEntityFrameworkVersionArgument(targetEntityFrameworkVersion, "targetEntityFrameworkVersion");
 
            EntityUtil.CheckStringArgument(sourceEdmSchemaFilePath, "sourceEdmSchemaFilePath");
            EDesignUtil.CheckArgumentNull(additionalEdmSchemaFilePaths, "additionalEdmSchemaFilePaths");
            EntityUtil.CheckStringArgument(targetPath, "targetPath");
            EDesignUtil.CheckTargetEntityFrameworkVersionArgument(targetEntityFrameworkVersion, "targetEntityFrameworkVersion");
 
            return InternalGenerateCode(sourceEdmSchemaFilePath, new LazyTextWriterCreator(targetPath), additionalEdmSchemaFilePaths, targetEntityFrameworkVersion);
        }
 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
        [ResourceExposure(ResourceScope.Machine)] // for sourceEdmSchemaFilePath, targetPath and additionalEdmSchema which are machine resources
        [ResourceConsumption(ResourceScope.Machine)] // for InternalGenerateCode method call. But the path is not created in this method.
        public IList<EdmSchemaError> GenerateCode(string sourceEdmSchemaFilePath, string targetPath, IEnumerable<string> additionalEdmSchemaFilePaths)
        {
            Version targetFrameworkVersion;
            IList<EdmSchemaError> errors = GetMinimumTargetFrameworkVersion(sourceEdmSchemaFilePath, out targetFrameworkVersion);
 
            if (errors.Where(e => e.Severity == EdmSchemaErrorSeverity.Error).Any())
            {
                return errors;
            }
 
            return errors.Concat(GenerateCode(sourceEdmSchemaFilePath, targetPath, additionalEdmSchemaFilePaths,
                targetFrameworkVersion)).ToList();
        }
 
        /// <summary>
        /// Creates a source code file that contains the object layer code generated from the specified conceptual schema
        /// definition language (CSDL) file. Note that the targetEntityFrameworkVersion parameter uses internal Entity
        /// Framework version numbers as described in the <see cref="EntityFrameworkVersions"/> class.
        /// </summary>
        /// <param name="sourceEdmSchemaFilePath">The path of the CSDL file.</param>
        /// <param name="targetPath">The path of the file that contains the generated object layer code.</param>
        /// <param name="targetEntityFrameworkVersion">The internal Entity Framework version that is being targeted.</param>
        /// <returns>A list of <see cref="EdmSchemaError"/> objects that contains any generated errors.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
        [ResourceExposure(ResourceScope.Machine)] // for sourceEdmSchemaFilePath and targetPath which are Machine resources
        [ResourceConsumption(ResourceScope.Machine)] // for InternalGenerateCode method call. But the path is not created in this method.
        public IList<EdmSchemaError> GenerateCode(string sourceEdmSchemaFilePath, string targetPath, Version targetEntityFrameworkVersion)
        {
            EntityUtil.CheckStringArgument(sourceEdmSchemaFilePath, "sourceEdmSchemaFilePath");
            EntityUtil.CheckStringArgument(targetPath, "targetPath");
            EDesignUtil.CheckTargetEntityFrameworkVersionArgument(targetEntityFrameworkVersion, "targetEntityFrameworkVersion");
 
            return InternalGenerateCode(sourceEdmSchemaFilePath, new LazyTextWriterCreator(targetPath), null, targetEntityFrameworkVersion);
        }
 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
        [ResourceExposure(ResourceScope.Machine)] // for sourceEdmSchemaFilePath and targetPath which are Machine resources
        [ResourceConsumption(ResourceScope.Machine)] // for InternalGenerateCode method call. But the path is not created in this method.
        public IList<EdmSchemaError> GenerateCode(string sourceEdmSchemaFilePath, string targetPath)
        {
            Version targetFrameworkVersion;
            IList<EdmSchemaError> errors = GetMinimumTargetFrameworkVersion(sourceEdmSchemaFilePath, out targetFrameworkVersion);
 
            if (errors.Where(e => e.Severity == EdmSchemaErrorSeverity.Error).Any())
            {
                return errors;
            }
 
            return errors.Concat(GenerateCode(sourceEdmSchemaFilePath, targetPath, targetFrameworkVersion)).ToList();
        }
 
        /// <summary>
        /// Generates object layer code using the conceptual schema definition language (CSDL) specified in the
        /// XmlReader object, and outputs the generated code to a TextWriter.
        /// Note that the targetEntityFrameworkVersion parameter uses internal EntityFramework version numbers as
        /// described in the <see cref="EntityFrameworkVersions"/> class.
        /// </summary>
        /// <param name="sourceEdmSchema">An XmlReader that contains the CSDL.</param>
        /// <param name="target">The TextWriter to which the object layer code is written.</param>
        /// <param name="targetEntityFrameworkVersion">The internal Entity Framework version that is being targeted.</param>
        /// <returns>A list of <see cref="EdmSchemaError"/> objects that contains any generated errors.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
        [ResourceConsumption(ResourceScope.Machine)] // temp file creation, and passing to InternalGenerateCode
        public IList<EdmSchemaError> GenerateCode(XmlReader sourceEdmSchema, TextWriter target, Version targetEntityFrameworkVersion)
        {
            EDesignUtil.CheckArgumentNull(sourceEdmSchema, "sourceEdmSchema");
            EDesignUtil.CheckArgumentNull(target, "target");
            EDesignUtil.CheckTargetEntityFrameworkVersionArgument(targetEntityFrameworkVersion, "targetEntityFrameworkVersion");
 
            Version schemaVersion;
            if (!IsValidSchema(sourceEdmSchema, out schemaVersion))
            {
                return new List<EdmSchemaError>() { CreateSourceEdmSchemaNotValidError() };
            }
 
            using (TempFileCollection collection = new TempFileCollection())
            {
                string tempSourceEdmSchemaPath = collection.AddExtension(XmlConstants.CSpaceSchemaExtension);
                SaveXmlReaderToFile(sourceEdmSchema, tempSourceEdmSchemaPath);
 
                return InternalGenerateCode(tempSourceEdmSchemaPath, schemaVersion, new LazyTextWriterCreator(target), null, targetEntityFrameworkVersion);
            }
        }
 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
        [ResourceConsumption(ResourceScope.Machine)] // temp file creation, and passing to InternalGenerateCode
        public IList<EdmSchemaError> GenerateCode(XmlReader sourceEdmSchema, TextWriter target)
        {
            Version targetFrameworkVersion;
            IList<EdmSchemaError> errors = GetMinimumTargetFrameworkVersion(sourceEdmSchema, out targetFrameworkVersion);
 
            if (errors.Where(e => e.Severity == EdmSchemaErrorSeverity.Error).Any())
            {
                return errors;
            }
 
            return errors.Concat(GenerateCode(sourceEdmSchema, target, targetFrameworkVersion)).ToList();
        }
 
        /// <summary>
        /// Creates a source code file that contains the object layer code generated from the specified conceptual schema
        /// definition language (CSDL) file. Note that the targetEntityFrameworkVersion parameter uses internal Entity
        /// Framework version numbers as described in the <see cref="EntityFrameworkVersions"/> class.
        /// </summary>
        /// <param name="sourceEdmSchema">An XmlReader that contains the CSDL.</param>
        /// <param name="target">The TextWriter to which the object layer code is written.</param>
        /// <param name="additionalEdmSchemas">
        /// A list of XmlReader objects that contain schemas that are referenced by the source schema (the CSDL).
        /// If the source schema does not have any dependencies, pass in an empty IList object.
        /// </param>
        /// <param name="targetEntityFrameworkVersion">The internal Entity Framework version that is being targeted.</param>
        /// <returns>A list of <see cref="EdmSchemaError"/> objects that contains any generated errors.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
        [ResourceConsumption(ResourceScope.Machine)] //Temp file creation, and passing to InternalGenerateCode
        public IList<EdmSchemaError> GenerateCode(XmlReader sourceEdmSchema, TextWriter target, IEnumerable<XmlReader> additionalEdmSchemas, Version targetEntityFrameworkVersion)
        {
            EDesignUtil.CheckArgumentNull(sourceEdmSchema, "sourceEdmSchema");
            EDesignUtil.CheckArgumentNull(additionalEdmSchemas, "additionalEdmSchemas");
            EDesignUtil.CheckArgumentNull(target, "target");
            EDesignUtil.CheckTargetEntityFrameworkVersionArgument(targetEntityFrameworkVersion, "targetEntityFrameworkVersion");
 
            Version schemaVersion;
            if (!IsValidSchema(sourceEdmSchema, out schemaVersion))
            {
                return new List<EdmSchemaError>() { CreateSourceEdmSchemaNotValidError() };
            }
 
            using (TempFileCollection collection = new TempFileCollection())
            {
 
                string tempSourceEdmSchemaPath = collection.AddExtension(XmlConstants.CSpaceSchemaExtension);
                SaveXmlReaderToFile(sourceEdmSchema, tempSourceEdmSchemaPath);
                List<string> additionalTempPaths = new List<string>();
                foreach (XmlReader reader in additionalEdmSchemas)
                {
 
                    string temp = Path.GetTempFileName() + XmlConstants.CSpaceSchemaExtension;
                    SaveXmlReaderToFile(reader, temp);
                    additionalTempPaths.Add(temp);
                    collection.AddFile(temp, false);
                }
                return InternalGenerateCode(tempSourceEdmSchemaPath, schemaVersion, new LazyTextWriterCreator(target), additionalTempPaths, targetEntityFrameworkVersion);
            }
        }
 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
        [ResourceConsumption(ResourceScope.Machine)] //Temp file creation, and passing to InternalGenerateCode
        public IList<EdmSchemaError> GenerateCode(XmlReader sourceEdmSchema, TextWriter target, IEnumerable<XmlReader> additionalEdmSchemas)
        {
            Version targetFrameworkVersion;
            IList<EdmSchemaError> errors = GetMinimumTargetFrameworkVersion(sourceEdmSchema, out targetFrameworkVersion);
 
            if (errors.Where(e => e.Severity == EdmSchemaErrorSeverity.Error).Any())
            {
                return errors;
            }
 
            return errors.Concat(GenerateCode(sourceEdmSchema, target, additionalEdmSchemas, targetFrameworkVersion)).ToList();
        }
 
        // this overload is for entry points that don't pass in readers, so we need to actually create a reader to get the schemaVersion out
        private IList<EdmSchemaError> InternalGenerateCode(string sourceEdmSchemaFilePath, LazyTextWriterCreator textWriter, IEnumerable<string> additionalEdmSchemaFilePaths, Version targetFrameworkVersion)
        {
            // do this check to maintain backwards compatibility with the behavior shipped in 
            // framework v4
            if (!File.Exists(sourceEdmSchemaFilePath))
            {
                return new List<EdmSchemaError>() { new EdmSchemaError(Strings.EdmSchemaFileNotFound(sourceEdmSchemaFilePath), (int)ModelBuilderErrorCode.FileNotFound, EdmSchemaErrorSeverity.Error, sourceEdmSchemaFilePath, 0, 0) };
            }
 
            using (XmlReader reader = XmlReader.Create(sourceEdmSchemaFilePath))
            {
                Version schemaVersion;
                if (!IsValidSchema(reader, out schemaVersion))
                {
                    return new List<EdmSchemaError>() { CreateSourceEdmSchemaNotValidError() };
                }
 
                return InternalGenerateCode(sourceEdmSchemaFilePath, schemaVersion, textWriter, additionalEdmSchemaFilePaths, targetFrameworkVersion);
            }
        }
 
        private bool IsValidSchema(XmlReader reader, out Version entityFrameworkVersion)
        {
            double schemaVersion;
            DataSpace dataSpace;
            if (System.Data.EntityModel.SchemaObjectModel.SchemaManager.TryGetSchemaVersion(reader, out schemaVersion, out dataSpace) &&
                dataSpace == DataSpace.CSpace)
            {
                entityFrameworkVersion = EntityFrameworkVersionsUtil.ConvertToVersion(schemaVersion);
                return true;
            }
            else if (EntityFrameworkVersions.TryGetEdmxVersion(reader, out entityFrameworkVersion))
            {
                return true;
            }
 
            return false;
        }
 
        private IList<EdmSchemaError> GetMinimumTargetFrameworkVersion(string sourceEdmSchemaFilePath, out Version targetFrameworkVersion)
        {
            targetFrameworkVersion = null;
            try
            {
                using (XmlReader reader = XmlReader.Create(sourceEdmSchemaFilePath))
                {
                    return GetMinimumTargetFrameworkVersion(reader, out targetFrameworkVersion);
                }
            }
            catch (System.IO.FileNotFoundException ex)
            {
                return new List<EdmSchemaError>() { EntityClassGenerator.CreateErrorForException(ModelBuilderErrorCode.FileNotFound, ex) };
            }
        }
 
        private IList<EdmSchemaError> GetMinimumTargetFrameworkVersion(XmlReader sourceEdmSchemaXmlReader, out Version targetFrameworkVersion)
        {
            List<EdmSchemaError> errorList = new List<EdmSchemaError>();
            if (!IsValidSchema(sourceEdmSchemaXmlReader, out targetFrameworkVersion))
            {
                errorList.Add(CreateSourceEdmSchemaNotValidError());
                return errorList;
            }
 
            if (targetFrameworkVersion < EntityFrameworkVersions.Version2)
            {
                targetFrameworkVersion = EntityFrameworkVersions.Version2;
            }
 
            if (targetFrameworkVersion > EntityFrameworkVersions.Default)
            {
                errorList.Add(new EdmSchemaError(Strings.DefaultTargetVersionTooLow(EntityFrameworkVersions.Default, targetFrameworkVersion), (int)ModelBuilderErrorCode.SchemaVersionHigherThanTargetVersion, EdmSchemaErrorSeverity.Warning));
            }
 
            return errorList;
        }
 
        private IList<EdmSchemaError> InternalGenerateCode(string sourceEdmSchemaFilePath, Version schemaVersion, LazyTextWriterCreator textWriter, IEnumerable<string> additionalEdmSchemaFilePaths, Version targetFrameworkVersion)
        {
            List<EdmSchemaError> errors = new List<EdmSchemaError>();
            try
            {
                if (targetFrameworkVersion == EntityFrameworkVersions.Version1)
                {
                    errors.Add(new EdmSchemaError(Strings.EntityCodeGenTargetTooLow, (int)ModelBuilderErrorCode.TargetVersionNotSupported, EdmSchemaErrorSeverity.Error));
                    return errors;
                }
 
                if (!MetadataItemCollectionFactory.ValidateActualVersionAgainstTarget(targetFrameworkVersion, schemaVersion, errors))
                {
                    return errors;
                }
 
                if (schemaVersion == EntityFrameworkVersions.EdmVersion1_1)
                {
                    return GenerateCodeFor1_1Schema(sourceEdmSchemaFilePath, textWriter, additionalEdmSchemaFilePaths);
                }
 
                ReflectionAdapter codeGenerator = ReflectionAdapter.Create(_languageOption, targetFrameworkVersion);
                codeGenerator.SourceCsdlPath = sourceEdmSchemaFilePath;
                codeGenerator.ReferenceCsdlPaths = additionalEdmSchemaFilePaths;
                codeGenerator.EdmToObjectNamespaceMap = _edmToObjectNamespaceMap.AsDictionary();
 
                string code = codeGenerator.TransformText();
 
                if (codeGenerator.Errors.Count != 0)
                {
                    ModelBuilderErrorCode errorCode = ModelBuilderErrorCode.PreprocessTemplateTransformationError;
                    errors.AddRange(codeGenerator.Errors.OfType<CompilerError>().Select(c => ConvertToEdmSchemaError(c, errorCode)));
 
                    if (codeGenerator.Errors.HasErrors)
                        return errors;
                }
 
                using (TextWriter writer = textWriter.GetOrCreateTextWriter())
                {
                    writer.Write(code);
                }
            }
            catch (System.UnauthorizedAccessException ex)
            {
                errors.Add(EntityClassGenerator.CreateErrorForException(ModelBuilderErrorCode.SecurityError, ex));
            }
            catch (System.IO.FileNotFoundException ex)
            {
                errors.Add(EntityClassGenerator.CreateErrorForException(ModelBuilderErrorCode.FileNotFound, ex));
            }
            catch (System.Security.SecurityException ex)
            {
                errors.Add(EntityClassGenerator.CreateErrorForException(ModelBuilderErrorCode.SecurityError, ex));
            }
            catch (System.IO.DirectoryNotFoundException ex)
            {
                errors.Add(EntityClassGenerator.CreateErrorForException(ModelBuilderErrorCode.DirectoryNotFound, ex));
            }
            catch (System.IO.IOException ex)
            {
                errors.Add(EntityClassGenerator.CreateErrorForException(ModelBuilderErrorCode.IOException, ex));
            }
            catch (Exception e)
            {
                if (MetadataUtil.IsCatchableExceptionType(e))
                {
                    errors.Add(EntityClassGenerator.CreateErrorForException(ModelBuilderErrorCode.PreprocessTemplateTransformationError, e, sourceEdmSchemaFilePath));
                }
                else
                {
                    throw;
                }
            }
 
            return errors;
        }
 
        private static EdmSchemaError CreateSourceEdmSchemaNotValidError()
        {
            return new EdmSchemaError(Strings.EdmSchemaNotValid, (int)ModelBuilderErrorCode.SourceSchemaIsInvalid, EdmSchemaErrorSeverity.Error);
        }
 
        private IList<EdmSchemaError> GenerateCodeFor1_1Schema(string sourceEdmSchemaFilePath, LazyTextWriterCreator textWriter, IEnumerable<string> additionalEdmSchemaFilePaths)
        {
            EntityClassGenerator generator = new EntityClassGenerator(_languageOption);
            string target = textWriter.TargetFilePath;
            bool cleanUpTarget = false;
            try
            {
                if (textWriter.IsUserSuppliedTextWriter)
                {
                    cleanUpTarget = true;
                    target = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());
 
                }
 
                IList<EdmSchemaError> errors = generator.GenerateCode(sourceEdmSchemaFilePath, target, additionalEdmSchemaFilePaths != null ? additionalEdmSchemaFilePaths : Enumerable.Empty<string>());
 
                if (textWriter.IsUserSuppliedTextWriter && !errors.Any(e => e.Severity == EdmSchemaErrorSeverity.Error))
                {
                    textWriter.GetOrCreateTextWriter().Write(File.ReadAllText(target));
                }
 
                return errors;
            }
            finally
            {
                if (cleanUpTarget && target != null && File.Exists(target))
                {
                    File.Delete(target);
                }
            }
 
        }
 
        private static void SaveXmlReaderToFile(XmlReader schema, string tempSchemaPath)
        {
            using (XmlWriter writer = XmlWriter.Create(tempSchemaPath))
            {
                writer.WriteNode(schema, false);
            }
        }
 
        private static EdmSchemaError ConvertToEdmSchemaError(CompilerError error, ModelBuilderErrorCode defaultErrorCode)
        {
            int errorNumber;
            string message = error.ErrorText;
            bool usePositionInfo = true;
            EdmSchemaErrorSeverity severity = error.IsWarning ? EdmSchemaErrorSeverity.Warning : EdmSchemaErrorSeverity.Error;
            if (int.TryParse(error.ErrorNumber, out errorNumber))
            {
                if (error.Line == -1 && error.Column == -1)
                {
                    usePositionInfo = false;
                }
            }
            else
            {
                message = String.Format(CultureInfo.InvariantCulture, "{0}({1})", error.ErrorText, error.ErrorNumber);
                errorNumber = (int)defaultErrorCode;
            }
 
            if (usePositionInfo)
            {
                return new EdmSchemaError(message,
                                        errorNumber,
                                        severity,
                                        error.FileName,
                                        error.Line,
                                        error.Column);
            }
            else
            {
                return new EdmSchemaError(message,
                                        errorNumber,
                                        severity);
            }
        }
 
 
        static Type _vbCodeGeneratorTypeV2 = null;
        static Type _vbCodeGeneratorTypeV3 = null;
        static Type _csharpCodeGeneratorTypeV2 = null;
        static Type _csharpCodeGeneratorTypeV3 = null;
 
        private const string CSharpTemplateCodeGenTypeName = "TemplateCodeGenerators.CSharpCodeGenTemplate";
        private const string VBTemplateCodeGenTypeName = "TemplateCodeGenerators.VBCodeGenTemplate";
        private const string CSharpTemplateCodeGenV3TypeName = CSharpTemplateCodeGenTypeName + "V50";
        private const string VBTemplateCodeGenV3TypeName = VBTemplateCodeGenTypeName + "V50";
        private const string CSharpTemplateCodeGenResourceV2 = CSharpTemplateCodeGenTypeName + ".cs";
        private const string CSharpTemplateCodeGenResourceV3 = CSharpTemplateCodeGenTypeName + "V5.0.cs";
        private const string VBTemplateCodeGenResourceV2 = VBTemplateCodeGenTypeName + ".vb";
        private const string VBTemplateCodeGenResourceV3 = VBTemplateCodeGenTypeName + "V5.0.vb";
 
        private static object CreateCSharpCodeGeneratorV2()
        {
            if (_csharpCodeGeneratorTypeV2 == null)
            {
                Type type = CreateCodeGeneratorType(new Microsoft.CSharp.CSharpCodeProvider(), CSharpTemplateCodeGenResourceV2, CSharpTemplateCodeGenTypeName);
                Interlocked.Exchange(ref _csharpCodeGeneratorTypeV2, type);
            }
 
            return Activator.CreateInstance(_csharpCodeGeneratorTypeV2);
        }
 
        private static object CreateCSharpCodeGeneratorV3()
        {
            if (_csharpCodeGeneratorTypeV3 == null)
            {
                Type type = CreateCodeGeneratorType(new Microsoft.CSharp.CSharpCodeProvider(), CSharpTemplateCodeGenResourceV3, CSharpTemplateCodeGenV3TypeName);
                Interlocked.Exchange(ref _csharpCodeGeneratorTypeV3, type);
            }
 
            return Activator.CreateInstance(_csharpCodeGeneratorTypeV3);
        }
 
        private static object CreateVBCodeGeneratorV2()
        {
            if (_vbCodeGeneratorTypeV2 == null)
            {
                Type type = CreateCodeGeneratorType(new Microsoft.VisualBasic.VBCodeProvider(), VBTemplateCodeGenResourceV2, VBTemplateCodeGenTypeName);
                Interlocked.Exchange(ref _vbCodeGeneratorTypeV2, type);
            }
 
            return Activator.CreateInstance(_vbCodeGeneratorTypeV2);
        }
 
        private static object CreateVBCodeGeneratorV3()
        {
            if (_vbCodeGeneratorTypeV3 == null)
            {
                Type type = CreateCodeGeneratorType(new Microsoft.VisualBasic.VBCodeProvider(), VBTemplateCodeGenResourceV3, VBTemplateCodeGenV3TypeName);
                Interlocked.Exchange(ref _vbCodeGeneratorTypeV3, type);
            }
 
            return Activator.CreateInstance(_vbCodeGeneratorTypeV3);
        }
 
        private static Type CreateCodeGeneratorType(System.CodeDom.Compiler.CodeDomProvider compilerProvider, string resourceName, string typeName)
        {
            string sourceCode = null;
            using (Stream sourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
            using (StreamReader reader = new StreamReader(sourceStream))
            {
                sourceCode = reader.ReadToEnd();
            }
 
            CompilerParameters compilerParams = new CompilerParameters();
            compilerParams.CompilerOptions = "/d:PREPROCESSED_TEMPLATE";
            compilerParams.GenerateInMemory = true;
            compilerParams.GenerateExecutable = false;
            // grab the assemblies by location so that we don't compile against one that we didn't reference
            compilerParams.ReferencedAssemblies.AddRange(new string[] {
                        typeof(System.CodeDom.Compiler.CodeDomProvider).Assembly.Location,              // System.dll
                        typeof(System.Linq.Enumerable).Assembly.Location,                               // System.Core.dll
                        typeof(System.Data.Objects.ObjectContext).Assembly.Location,                    // System.Data.Entity.dll
                        typeof(System.Data.Entity.Design.EntityCodeGenerator).Assembly.Location,        // System.Data.Entity.Design.dll
                        typeof(System.Data.DbType).Assembly.Location,                                   // System.Data.dll
                        typeof(System.Xml.XmlAttribute).Assembly.Location,                              // System.Xml.dll
                        typeof(System.Xml.Linq.XElement).Assembly.Location,                             // System.Xml.Linq.dll
                });
 
#if !ENABLE_TEMPLATE_DEBUGGING
            CompilerResults results = compilerProvider.CompileAssemblyFromSource(compilerParams, sourceCode);
#else
            // enables debugging
            compilerParams.GenerateInMemory = false;
            compilerParams.IncludeDebugInformation = true;
            string baseName = Path.GetFileNameWithoutExtension(Path.GetTempFileName()) + ".";
            compilerParams.OutputAssembly = Path.Combine(Path.GetTempPath(), baseName + typeName + ".Assembly.dll");
            string sourceFileName = Path.Combine(Path.GetTempPath(), baseName + typeName + ".Source." + compilerProvider.FileExtension);
            File.WriteAllText(sourceFileName, sourceCode);
            CompilerResults results = compilerProvider.CompileAssemblyFromFile(compilerParams, sourceFileName);
#warning DO NOT CHECK IN LIKE THIS, Dynamic Assembly Debugging is enabled
#endif
 
 
            if (results.Errors.HasErrors)
            {
                string message = results.Errors.OfType<CompilerError>().Aggregate(string.Empty, (accumulated, input) => accumulated == string.Empty ? input.ToString() : accumulated + Environment.NewLine + input.ToString());
                throw EDesignUtil.InvalidOperation(message);
            }
 
            return results.CompiledAssembly.GetType(typeName);
        }
 
 
 
        private class ReflectionAdapter
        {
            private object _instance;
            private MethodInfo _transformText;
            private PropertyInfo _sourceCsdlPath;
            private PropertyInfo _referenceCsdlPaths;
            private PropertyInfo _errors;
            private PropertyInfo _edmToObjectNamespaceMap;
 
            internal ReflectionAdapter(object instance)
            {
                _instance = instance;
                Type type = _instance.GetType();
                BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
                _transformText = type.GetMethod("TransformText", flags, null, new Type[0], null);
                Debug.Assert(_transformText != null, "Unable to find method, did the signature or name change?");
 
                _sourceCsdlPath = type.GetProperty("SourceCsdlPath", flags, null, typeof(String), new Type[0], null);
                Debug.Assert(_sourceCsdlPath != null, "Unable to find property, did the signature or name change?");
 
                _referenceCsdlPaths = type.GetProperty("ReferenceCsdlPaths", flags, null, typeof(IEnumerable<String>), new Type[0], null);
                Debug.Assert(_referenceCsdlPaths != null, "Unable to find property, did the signature or name change?");
 
                _errors = type.GetProperty("Errors", flags, null, typeof(CompilerErrorCollection), new Type[0], null);
                Debug.Assert(_errors != null, "Unable to find property, did the signature or name change?");
 
                _edmToObjectNamespaceMap = type.GetProperty("EdmToObjectNamespaceMap", flags, null, typeof(Dictionary<string, string>), new Type[0], null);
                Debug.Assert(_edmToObjectNamespaceMap != null, "Unable to find property, did the signature or name change?");
            }
 
            internal CompilerErrorCollection Errors
            {
                get { return (CompilerErrorCollection)_errors.GetValue(_instance, null); }
            }
 
            internal IEnumerable<String> ReferenceCsdlPaths
            {
                set
                {
                    _referenceCsdlPaths.SetValue(_instance, value, null);
                }
            }
 
            internal string SourceCsdlPath
            {
                get
                {
                    return (String)_sourceCsdlPath.GetValue(_instance, null);
                }
                set
                {
                    _sourceCsdlPath.SetValue(_instance, value, null);
                }
            }
 
            internal Dictionary<string, string> EdmToObjectNamespaceMap
            {
                set { _edmToObjectNamespaceMap.SetValue(_instance, value, null); }
            }
 
            internal string TransformText()
            {
                try
                {
                    return (string)_transformText.Invoke(_instance, new object[0]);
                }
                catch (TargetInvocationException e)
                {
                    Exception actual = e.InnerException != null ? e.InnerException : e;
 
                    System.CodeDom.Compiler.CompilerError error = new System.CodeDom.Compiler.CompilerError();
                    error.ErrorText = actual.Message;
                    error.IsWarning = false;
                    error.FileName = SourceCsdlPath;
                    Errors.Add(error);
 
                    return string.Empty;
                }
 
            }
 
            internal static ReflectionAdapter Create(LanguageOption language, Version targetEntityFrameworkVersion)
            {
                if (language == LanguageOption.GenerateCSharpCode)
                {
                    if (targetEntityFrameworkVersion >= EntityFrameworkVersions.Latest)
                    {
                        return new ReflectionAdapter(CreateCSharpCodeGeneratorV3());
                    }
                    else
                    {
                        return new ReflectionAdapter(CreateCSharpCodeGeneratorV2());
                    }
                }
                else
                {
                    Debug.Assert(language == LanguageOption.GenerateVBCode, "Did you add a new option?");
                    if (targetEntityFrameworkVersion >= EntityFrameworkVersions.Latest)
                    {
                        return new ReflectionAdapter(CreateVBCodeGeneratorV3());
                    }
                    else
                    {
                        return new ReflectionAdapter(CreateVBCodeGeneratorV2());
                    }
                }
            }
        }
    }
}