使用 XSLT 转换实体框架 EDMX 文件

发布于 2024-09-16 09:56:28 字数 599 浏览 3 评论 0原文

我想对 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 对视图进行这些更改,以便在重新生成模型时它们不会被删除。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

黯淡〆 2024-09-23 09:56:28

“很难说出这里问的是什么”;)

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

我从事 EF 项目,并使用 XSLT 来后处理 edmx 文件和/或生成附加代码。这是在构建期间手动或从批处理文件调用的。

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

"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.

电影里的梦 2024-09-23 09:56:28

我意识到这有点过时了,但我最近找到了一个在保存时转换 Edmx 的解决方案,我想我会分享它。请注意,我们使用的是 Visual Studio 2012、Entity Framework 6.0 和 .Net 4.5。我们没有使用 Code First。

我们的问题是,通过实体框架生成的视图具有我们不想要的额外主键列(而不是没有我们需要的列)。我们无法对视图创建唯一性约束,因为视图引用了非确定性函数。因此,这意味着正确查看关键列的唯一方法是更新 edmx 文件。

为了实现这一目标,我更新了 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. Transaction_Detail_Base上的Version字段添加ConcurrencyFixed; 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

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
濫情▎り 2024-09-23 09:56:28

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

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

HTH。如果这没有帮助,请提供更多细节。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文