Transforming Entity Framework EDMX File with XSLT

I would like to make some changes to my EF4 edmx file without modifying the file itself, mainly so I don't loose all my changes if I regenerate the model from the database. I'm familiar with XSL and have seen references made to using it in conjunction with the edmx file. This sounds like a great solution, however I can't seem to find any documentation on how to actually set this up. Do you reference the style sheet from the edmx file or do you configure it to look at the template and then load the edmx file in somehow? Any resources on this are appreciated.

Clarification:

Specifically what I'm trying to do is modify the model so that several of the views act as tables with relations within the model, see here: http://blogs.msdn.com/b/alexj/archive/2009/09/01/tip-34-how-to-work-with-updatable-views.aspx

The issue I'll have in using that method is if I need to update the database and regenerate the model I'll have to go back and make all of those changes again, I was hoping there was a way to use xslt to make those changes to the views so they would not be removed when the model is regenerated.

Answers


"It's difficult to tell what is being asked here" ;)

What do you mean by "make some changes to my EF4 edmx file without modifying the file itself". Do you want to create a derived edmx from the original? If so, you need to be aware that C# code (= class definitions) is automatically generated during Save.

I worked on EF projects, and used XSLT to post-process edmx files and/or generate additional code. This was invoked either manually or from a batch file during build.

You can invoke the XSLT from a simple Powershell script using the .Net framework. My blog posts on EF (3.5) may help you to understand edmx processing.


I realise this is a bit out of date, but I have recently found a solution to transforming an Edmx on Save which I thought I'd share. Note that we are using Visual Studio 2012, Entity Framework 6.0 and .Net 4.5. We are not using Code First.

Our issue was that the Views generated via the Entity Framework had additional primary key columns that we did not want (rather than not having columns we required). We couldn't create uniqueness constraints on the Views because the views referenced a non deterministic function. So that meant the only way to get the View key columns correct was to update the edmx file.

To achieve this I updated the T4 template to load the edmx file, translate it using xslt, and save it again. This means every time a developer saves the edmx file from the designer window it will be updated correctly before the the .cs class is generated. As an additional check I also created a powershell script to check the primary keys during our automated builds.

Here's some sample code (inside our Model1.tt file near the top).

<#@ template language="C#" debug="false" hostspecific="true"#>
<#@ include file="EF.Utility.CS.ttinclude"#><#@ output extension=".cs"#>
<#@ assembly name="System.Xml" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Xml.Xsl" #>
<#
XmlDocument rawXDoc = new XmlDocument();
XmlDocument xDoc = new XmlDocument();
XmlReaderSettings settings = new XmlReaderSettings {
    //ConformanceLevel = ConformanceLevel.Document;
    DtdProcessing = DtdProcessing.Prohibit
};
//Note that to use the Host.ResolvePath below you must set hostspecific="true" in the template directive.
using (FileStream rawDocFileSteam =    File.OpenRead(Host.ResolvePath("MyDataModel.edmx"))) {
    using (XmlReader rawDocReader = XmlReader.Create(rawDocFileSteam, settings)) {
        using (XmlTextReader xsltReader = new XmlTextReader(Host.ResolvePath("DataModelTransform.xslt")) ) {
            XslCompiledTransform xsltTransform = new XslCompiledTransform();
            xsltTransform.Load(xsltReader); //Ensure the XML Resolver is null, or a XmlSecureResolver to prevent a Billion Laughs denial of service.
            using (MemoryStream ms = new MemoryStream()) {
                xsltTransform.Transform(rawDocReader, null, ms);
                ms.Position = 0;
                xDoc.Load(ms);
            }
        }
    }
}

xDoc.Save(Host.ResolvePath("MyDataModel.edmx"));

#>

Here's an example of the xslt file we use. It does two things. 1. Adds the ConcurrencyFixed to the Version field on Transaction_Detail_Base; and 2. Removes invalid Primary Key columns.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:ssdl="http://schemas.microsoft.com/ado/2009/11/edm/ssdl"
                xmlns:edmx="http://schemas.microsoft.com/ado/2009/11/edmx"
                xmlns:edm="http://schemas.microsoft.com/ado/2009/11/edm"
                xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation"
  >
  <xsl:output method="xml" indent="yes"/>

  <!--Ensure blank lines aren't left when we remove invalid PrimaryKey fields.-->
  <xsl:strip-space  elements="*"/>

  <xsl:template match="@*|*|processing-instruction()|comment()">
    <xsl:call-template name="CopyDetails"/>
  </xsl:template>

  <xsl:template name="CopyDetails">
    <xsl:copy>
      <xsl:apply-templates select="@*|*|text()|processing-instruction()|comment()"/>
    </xsl:copy>
  </xsl:template>

  <!--Set concurrency mode to fixed for Transaction_Detail_Base.-->
  <xsl:template match="edmx:ConceptualModels/edm:Schema/edm:EntityType[@Name='Transaction_Detail_Base']/edm:Property[@Name='Version']">
    <xsl:call-template name="AddConcurrencyAttribute"/>
  </xsl:template>

  <!-- Add the ConcurrencyAttribute if it doesn't exist, otherwise update it if it does -->
  <xsl:template name="AddConcurrencyAttribute">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
      <xsl:attribute name="ConcurrencyMode">Fixed</xsl:attribute>
   </xsl:copy>
  </xsl:template>

  <!-- Remove unused primary key columns from views. Should be removed from StorageMode and ConceptualModels -->
  <!--Transaction_Detail. ssdl is the StorageModel section, edm is the ConceptualModel section-->
  <xsl:template match="ssdl:EntityType[@Name='Transaction_Detail']/ssdl:Key/ssdl:PropertyRef | edm:EntityType[@Name='Transaction_Detail']/edm:Key/edm:PropertyRef">
    <xsl:if test="@Name='Asset' or @Name='Date' or @Name='Portfolio' or @Name='System_Reference'">
      <xsl:call-template name="CopyDetails"/>
    </xsl:if>
  </xsl:template>


</xsl:stylesheet>

Finally, here's an example of the Powershell script used to verify the .edmx when building.

function IsValidViewNode([string]$viewName, [string[]]$keyFields, [int]$typeCheck)
{
    [System.Xml.XmlNodeList]$nodelist = $null;
    if ( $typeCheck -eq 1 ) {
        $nodelist = $Xml.SelectNodes("/edmx:Edmx/edmx:Runtime/edmx:StorageModels/ssdl:Schema/ssdl:EntityType[@Name='$viewName']/ssdl:Key/ssdl:PropertyRef", $nsmgr)
    } else
    {
        $nodelist = $Xml.SelectNodes("/edmx:Edmx/edmx:Runtime/edmx:ConceptualModels/edm:Schema/edm:EntityType[@Name='$viewName']/edm:Key/edm:PropertyRef", $nsmgr)
    }
    [int] $matchedItems = 0
    [int] $unmatchedItems = 0
    if ($nodelist -eq $null -or $nodelist.Count -eq 0)
    {
        return $false;
    }
    foreach ($node in $nodelist) {
                $name = ""
                if ($node -ne $null -and $node.Attributes -ne $null -and $node.Attributes -contains "Name" -ne $null )
                {
                    $name = $node.Name
                }
                #Write-Host $name
                if ($keyFields -contains $name) {
                    $matchedItems++
                }
                else {
                    $unmatchedItems++
                }
                #Write-Host $matchedItems
                #Write-Host $unmatchedItems
                #Write-Host $keyFields.Length
            }
    #Right Pad the detail string.,
    $resultString = "Primary Keys for $viewName" + (" " * (50 - "Primary Keys for $viewName".Length))
    if ( $matchedItems -eq $keyFields.Length -and $unmatchedItems -eq 0 ) {
        Write-Host $resultString - Valid
        return ""
    }
    else {
        Write-Host $resultString - INVALID
        return "$viewName,"
    }
}
[string]$PKErrors = ""
# Read the xml file
$xml = [xml](Get-Content 'RALPHDataModel.edmx') 
$nsmgr = new-object Xml.XmlNamespaceManager($Xml.NameTable)
$nsmgr.AddNamespace("edmx", "http://schemas.microsoft.com/ado/2009/11/edmx")
$nsmgr.AddNamespace("ssdl", "http://schemas.microsoft.com/ado/2009/11/edm/ssdl")
$nsmgr.AddNamespace("edm", "http://schemas.microsoft.com/ado/2009/11/edm")
<# 
 ***
 *** VERIFY PRIMARY KEY COLUMNS FOR VIEWS ***
 *** This ensures the developer has run the DataModel.xslt to fix up the .edmx file.
 ***
#>
$PKErrors = $PKErrors + (IsValidViewNode "Transaction_Detail" ("Asset","Date","Portfolio","System_Reference") 1)
$ExitCode = 0
if ($PKErrors -ne "" ) {
    Write-Host "Invalid Primary Keys for Views: " + $PKErrors.TrimEnd(",")
    $ExitCode = 100
}
Exit $ExitCode

I don't know anything about EF4 itself, but how about this: Suppose your original edmx file (regenerated from the db) is "A.edmx". When you give EF4 the name of the edmx file, give it a URL (if allowed) "http://localhost/B.edmx". Set up a simple web service (I don't mean SOAP but simple XML) that responds to this URL with the result of transforming A.edmx with your XSLT stylesheet.

Alternatively, avoid the web service part and have your application check the timestamp of B.edmx against A.edmx; if A is newer, or B doesn't exist, have it run an XSLT processor to transform A.edmx to B.edmx.

HTH. If that doesn't help, please give some more specifics.


Need Your Help

Ignore T When Casting to a Generic

c# generics

I'm trying to cut out some additional calls and I'm making my 5 minute problem a 30 minute problem. Is there a way to cast an object without knowing its generic type? In the code below, I would lik...

How can I check my glue record ip update was successful?

dns nameservers

I changed the ip address of the glue records at my domain registrar (Moniker). But still the name servers are pointing to the old server. How can I verify that the updation was successful?