使用XSLT转换Entity Framework EDMX文件

3
我想对我的EF4 edmx文件进行一些更改,但不想修改该文件本身,主要是因为如果我从数据库重新生成模型,我就会失去所有更改。我熟悉XSL,并看到有关将其与edmx文件一起使用的参考。这听起来是一个很好的解决方案,但我似乎找不到有关如何实际设置它的文档。您是从edmx文件引用样式表还是配置它以查看模板,然后以某种方式加载edmx文件?任何相关资源都将不胜感激。
澄清: 具体来说,我试图修改模型,使得几个视图在模型内部具有关系表的功能,请参见此处: http://blogs.msdn.com/b/alexj/archive/2009/09/01/tip-34-how-to-work-with-updatable-views.aspx 使用该方法时遇到的问题是,如果需要更新数据库并重新生成模型,则必须返回并再次进行所有这些更改,我希望有一种方法可以使用xslt对视图进行更改,以便在重新生成模型时不会删除它们。

听起来问题的一半是由于“更新模型”向导的工作方式;通过替换SSDL并覆盖更改。如果您想要一种更有选择性的方式来更新模型(仅更新要更新的部分),可以查看我为EF4开发的Model Comparer。您可以在此处查看介绍screencast:http://huagati.blogspot.com/2010/07/introducing-model-comparer-for-entity.html ... 并从 http://www.huagati.com/dbmltools/ 下载它+获取试用许可证。 - KristoferA
3个回答

4
我知道这篇文章有点过时,但是我最近发现了一个解决方案,可以在保存Edmx文件时进行转换。请注意,我们使用的是Visual Studio 2012,Entity Framework 6.0和.Net 4.5,而不是使用Code First。
我们遇到的问题是,通过Entity Framework生成的Views具有额外的主键列,而我们不需要这些列(我们需要的列却没有)。由于视图引用了非确定性函数,因此我们无法在视图上创建唯一性约束。因此,唯一的方法是更新edmx文件以正确获取View的关键列。
为了实现这一点,我更新了T4模板,加载edmx文件,使用xslt进行翻译,并再次保存它。这意味着每当开发人员从设计窗口保存edmx文件时,它都会在生成.cs类之前正确地更新。作为额外的检查,我还创建了一个powershell脚本,在自动化构建期间检查主键。
以下是一些示例代码(位于Model1.tt文件的顶部附近)。
<#@ 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"));

#>

以下是我们使用的XSLT文件示例。它有两个作用:1. 将ConcurrencyFixed添加到Transaction_Detail_Base的Version字段中;2. 删除无效的主键列。

<?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>

最后,这里是用于构建时验证 .edmx 文件的 Powershell 脚本示例。

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

4

"很难确定这里正在询问什么" ;)

你所说的“在不修改文件本身的情况下对我的EF4 edmx文件进行一些更改”是什么意思?您想从原始文件创建一个派生的edmx文件吗?如果是这样,您需要知道在保存期间会自动生成C#代码(=类定义)。

我曾经参与过EF项目,并使用XSLT对edmx文件进行后处理和/或生成其他代码。这可以手动调用,也可以在构建期间从批处理文件中调用。

您可以使用.Net框架从简单的Powershell脚本调用XSLT。 我的EF博客文章(3.5)可能会帮助您了解edmx处理。


感谢您的回复,我尝试在上面进行了澄清。 - Graham Conzett

0

我对EF4本身一无所知,但是你可以这样做:假设你的原始edmx文件(从数据库重新生成)是“A.edmx”。当你给EF4提供edmx文件名时,给它一个URL(如果允许)“http://localhost/B.edmx”。设置一个简单的Web服务(我不是指SOAP而是简单的XML),该服务响应此URL并使用您的XSLT样式表转换A.edmx的结果。

或者,避免Web服务部分,并使您的应用程序检查B.edmx的时间戳是否比A.edmx新;如果A更新或B不存在,则运行XSLT处理器将A.edmx转换为B.edmx。

希望对你有所帮助。如果这没有帮助,请提供更多具体信息。


谢谢您的回复。我已经尝试在上面澄清我想要做什么。 - Graham Conzett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接