September 24, 2013

Entity Framework T4 view generator and .NET 4.5

I have been happily using Sanjay Nagamangalam's T4 template to pre-generate Entity Framework views, which substantially speeds up application startup. Unfortunately after upgrading the project to .NET 4.5, the T4 view generator failed with the following error:

Running transformation: System.ArgumentException: Argument 'xmlReaders' is not valid.  The set contains a null value.
   at System.Data.EntityUtil.CheckArgumentContainsNull[T](IEnumerable`1& enumerableArgument, String argumentName)
   at System.Data.Metadata.Edm.EdmItemCollection..ctor(IEnumerable`1 xmlReaders)
   at Microsoft.VisualStudio.TextTemplatingAED4E591A16DD045E483CD58B20DB126.GeneratedTextTransformation.GenerateViews(String edmxFilePath)

It turns out the original T4 template is incompatible with .NET 4.5. I've fixed it to support .NET 4.5. See below for the fixed code.

The fix consists of two changes to the original code:
  • XML namespaces for version 3 of EDMX have been added to the T4 template. The template now supports versions 1 through 3 of the EDMX format.
  • Correct EDMX version is passed as the third parameter to the EntityViewGenerator.GenerateViews method. The original script omitted this parameter.
The second point means that the view generator now requires .NET Framework 4 or higher. That's not a big deal since if you are reading this, you are likely using .NET 4.5 already.

<#
/***************************************************************************

Copyright (c) Microsoft Corporation. All rights reserved.

THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.

***************************************************************************/
#>

<#
    //
    // TITLE: T4 template to generate views for an EDMX file in a C# project
    //
    // DESCRIPTION:
    // This is a T4 template to generate views in C# for an EDMX file in C# projects.
    // The generated views are automatically compiled into the project's output assembly.
    //
    // This template follows a simple file naming convention to determine the EDMX file to process:
    // - It assumes that [edmx-file-name].Views.tt will process and generate views for [edmx-file-name].EDMX
    // - The views are generated in the code behind file [edmx-file-name].Views.cs
    //
    // USAGE:
    // Do the following to generate views for an EDMX file (e.g. Model1.edmx) in a C# project
    // 1. In Solution Explorer, right-click the project node and choose "Add...Existing...Item" from the context menu
    // 2. Browse to and choose this .tt file to include it in the project 
    // 3. Ensure this .tt file is in the same directory as the EDMX file to process 
    // 4. In Solution Explorer, rename this .tt file to the form [edmx-file-name].Views.tt (e.g. Model1.Views.tt)
    // 5. In Solution Explorer, right-click Model1.Views.tt and choose "Run Custom Tool" to generate the views
    // 6. The views are generated in the code behind file Model1.Views.cs
    //
    // TIPS:
    // If you have multiple EDMX files in your project then make as many copies of this .tt file and rename appropriately
    // to pair each with each EDMX file.
    //
    // To generate views for all EDMX files in the solution, click the "Transform All Templates" button in the Solution Explorer toolbar
    // (its the rightmost button in the toolbar) 
    //
#>

<#
    //
    // T4 template code follows
    //
#>

<#@ template language="C#" hostspecific="true"#>
<#@ output extension=".cs" #>

<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="System.Data.Entity" #>
<#@ assembly name="System.Data.Entity.Design" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>

<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data.Entity.Design" #>
<#@ import namespace="System.Data.Metadata.Edm" #>
<#@ import namespace="System.Data.Mapping" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Xml.Linq" #>

<# 
    // Find EDMX file to process: Model1.Views.tt generates views for Model1.EDMX
    string edmxFileName = Path.GetFileNameWithoutExtension(this.Host.TemplateFile).ToLowerInvariant().Replace(".views", "") + ".edmx";
    string edmxFilePath = Path.Combine(Path.GetDirectoryName(this.Host.TemplateFile), edmxFileName);
    if (File.Exists(edmxFilePath))
    {
        // Call helper class to generate pre-compiled views and write to output
        this.WriteLine(GenerateViews(edmxFilePath));
    }
    else
    {
        this.Error(String.Format("No views were generated. Cannot find file {0}. Ensure the project has an EDMX file and the file name of the .tt file is of the form [edmx-file-name].Views.tt", edmxFilePath));
    }
    
    // All done!
#>

<#+
    private String GenerateViews(string edmxFilePath)
    {
        String generatedViews = String.Empty;
        try
        {
            using (StreamWriter writer = new StreamWriter(new MemoryStream()))
            {
                XmlReader csdlReader = null;
                XmlReader mslReader = null;
                XmlReader ssdlReader = null;
                Version version;

                // Crack open the EDMX file and get readers over the CSDL, MSL and SSDL portions
                GetConceptualMappingAndStorageReaders(edmxFilePath, out csdlReader, out mslReader, out ssdlReader, out version);

                // Initialize item collections
                EdmItemCollection edmItems = new EdmItemCollection(new XmlReader[] { csdlReader });
                StoreItemCollection storeItems = new StoreItemCollection(new XmlReader[] { ssdlReader });
                StorageMappingItemCollection mappingItems = new StorageMappingItemCollection(edmItems, storeItems, new XmlReader[] { mslReader });

                // Initialize the view generator to generate views in C#
                EntityViewGenerator viewGenerator = new EntityViewGenerator();
                viewGenerator.LanguageOption = LanguageOption.GenerateCSharpCode;
                IList<EdmSchemaError> errors = viewGenerator.GenerateViews(mappingItems, writer, version);

                foreach (EdmSchemaError e in errors)
                {
                    // log error
                    this.Error(e.Message);
                }

                MemoryStream memStream = writer.BaseStream as MemoryStream;
                generatedViews = Encoding.UTF8.GetString(memStream.ToArray());
            }
        }
        catch (Exception ex)
        {
            // log error
            this.Error(ex.ToString());
        }

        return generatedViews;
    }

    private void GetConceptualMappingAndStorageReaders(string edmxFile, out XmlReader csdlReader, out XmlReader mslReader, out XmlReader ssdlReader, out Version version)
    {
        csdlReader = null;
        mslReader = null;
        ssdlReader = null;
        version = EntityFrameworkVersions.Version1;

        XNamespace edmxns;
        XNamespace csdlns;
        XNamespace mslns;
        XNamespace ssdlns;

        XNamespace edmxns_V1 = "http://schemas.microsoft.com/ado/2007/06/edmx";
        XNamespace edmxns_V2 = "http://schemas.microsoft.com/ado/2008/10/edmx";
        XNamespace edmxns_V3 = "http://schemas.microsoft.com/ado/2009/11/edmx";
        
        XDocument edmxDoc = XDocument.Load(edmxFile);
        if (edmxDoc != null)
        {
            // try to parse the Edmx file using V1 namespace
            XElement edmxNode = edmxDoc.Element(edmxns_V1 + "Edmx");
            if (edmxNode == null)
            {
                edmxNode = edmxDoc.Element(edmxns_V2 + "Edmx");
                if (edmxNode == null)
                {
                    // try to parse the Edmx file using V3 namespace
                    edmxNode = edmxDoc.Element(edmxns_V3 + "Edmx");
                    edmxns = "http://schemas.microsoft.com/ado/2009/11/edmx";
                    csdlns = "http://schemas.microsoft.com/ado/2009/11/edm";
                    mslns = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
                    ssdlns = "http://schemas.microsoft.com/ado/2009/11/edm/ssdl";
                    version = EntityFrameworkVersions.Version3;
                }
                else
                {
                    // the Edmx file is in V2 namespace
                    edmxns = "http://schemas.microsoft.com/ado/2008/10/edmx";
                    csdlns = "http://schemas.microsoft.com/ado/2008/09/edm";
                    mslns = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
                    ssdlns = "http://schemas.microsoft.com/ado/2009/02/edm/ssdl";
                    version = EntityFrameworkVersions.Version2;
                }
            }
            else
            {
                // the Edmx file is in V1 namespace
                edmxns = "http://schemas.microsoft.com/ado/2007/06/edmx";
                csdlns = "http://schemas.microsoft.com/ado/2006/04/edm";
                mslns = "urn:schemas-microsoft-com:windows:storage:mapping:CS";
                ssdlns = "http://schemas.microsoft.com/ado/2006/04/edm/ssdl";
            }

            if (edmxNode != null)
            {
                
                XElement runtimeNode = edmxNode.Element(edmxns + "Runtime");
                if (runtimeNode != null)
                {
                    // Create XmlReader over CSDL in EDMX
                    XElement conceptualModelsNode = runtimeNode.Element(edmxns + "ConceptualModels");
                    if (conceptualModelsNode != null)
                    {
                        XElement csdlContent = conceptualModelsNode.Element(csdlns + "Schema");
                        if (csdlContent != null)
                        {
                            csdlReader = csdlContent.CreateReader();
                        }
                    }

                    // Create XmlReader over MSL in EDMX
                    XElement mappingsNode = runtimeNode.Element(edmxns + "Mappings");
                    if (mappingsNode != null)
                    {
                        XElement mslContent = mappingsNode.Element(mslns + "Mapping");
                        if (mslContent != null)
                        {
                            mslReader = mslContent.CreateReader();
                        }
                    }

                    // Create XmlReader over SSDL in EDMX
                    XElement storageModelsNode = runtimeNode.Element(edmxns + "StorageModels");
                    if (storageModelsNode != null)
                    {
                        XElement ssdlContent = storageModelsNode.Element(ssdlns + "Schema");
                        if (ssdlContent != null)
                        {
                            ssdlReader = ssdlContent.CreateReader();
                        }
                    }
                }
            }
        }
    }
#>

Enjoy!

6 comments:

  1. Thanks a lot, dude! Really, I mean it! :)

    ReplyDelete
  2. Great. Thanks. You can now download this T4 from codeplex.
    https://ef6viewgenerator.codeplex.com/downloads/get/846148

    ReplyDelete
  3. Any new about the broken URL?
    Where Can I download it?

    I tried to copy&paste the source code but I receive the followig error:

    Error 1 Running transformation: System.Data.ProviderIncompatibleException: The provider did not return a ProviderManifest instance. ---> System.ArgumentException: Could not determine storage version; a valid storage connection or a version hint is required.
    at System.Data.SqlClient.SqlVersionUtils.GetSqlVersion(String versionHint)
    at System.Data.SqlClient.SqlProviderManifest..ctor(String manifestToken)
    at System.Data.SqlClient.SqlProviderServices.GetDbProviderManifest(String versionHint)
    at System.Data.Common.DbProviderServices.GetProviderManifest(String manifestToken)
    --- End of inner exception stack trace ---
    at System.Data.Common.DbProviderServices.GetProviderManifest(String manifestToken)
    at System.Data.Metadata.Edm.StoreItemCollection.Loader.InitializeProviderManifest(Action`3 addError)
    at System.Data.Metadata.Edm.StoreItemCollection.Loader.OnProviderManifestTokenNotification(String token, Action`3 addError)
    at System.Data.EntityModel.SchemaObjectModel.Schema.HandleProviderManifestTokenAttribute(XmlReader reader)
    at System.Data.EntityModel.SchemaObjectModel.Schema.HandleAttribute(XmlReader reader)
    at System.Data.EntityModel.SchemaObjectModel.SchemaElement.ParseAttribute(XmlReader reader)
    at System.Data.EntityModel.SchemaObjectModel.SchemaElement.Parse(XmlReader reader)
    at System.Data.EntityModel.SchemaObjectModel.Schema.HandleTopLevelSchemaElement(XmlReader reader)
    at System.Data.EntityModel.SchemaObjectModel.Schema.InternalParse(XmlReader sourceReader, String sourceLocation)
    at System.Data.EntityModel.SchemaObjectModel.Schema.Parse(XmlReader sourceReader, String sourceLocation)
    at System.Data.EntityModel.SchemaObjectModel.SchemaManager.ParseAndValidate(IEnumerable`1 xmlReaders, IEnumerable`1 sourceFilePaths, SchemaDataModelOption dataModel, AttributeValueNotification providerNotification, AttributeValueNotification providerManifestTokenNotification, ProviderManifestNeeded providerManifestNeeded, IList`1& schemaCollection)
    at System.Data.Metadata.Edm.StoreItemCollection.Loader.LoadItems(IEnumerable`1 xmlReaders, IEnumerable`1 sourceFilePaths)
    at System.Data.Metadata.Edm.StoreItemCollection.Init(IEnumerable`1 xmlReaders, IEnumerable`1 filePaths, Boolean throwOnError, DbProviderManifest& providerManifest, DbProviderFactory& providerFactory, String& providerManifestToken, Memoizer`2& cachedCTypeFunction)
    at System.Data.Metadata.Edm.StoreItemCollection..ctor(IEnumerable`1 xmlReaders)
    at Microsoft.VisualStudio.TextTemplating10967D970D6A6A2EC928E833F47BE3597044F27CEA90F392B1539963361BE1CA8FA03991BA5ED77C354E905AF9BD5DDE3369638DCEE327644C288C681D9168E4.GeneratedTextTransformation.GenerateViews(String edmxFilePath) 1 1

    any help?

    Thanks
    Daniele

    ReplyDelete
    Replies
    1. See mitaka's post, solved for me.

      http://stackoverflow.com/questions/19574981/entity-framework-the-provider-did-not-return-a-providermanifest-instance

      Delete
  4. Ok the link to use shold be this:
    http://ef5viewgenerator.codeplex.com/

    at least for .net 4.5.

    Bye

    ReplyDelete