【问题标题】:How can I extract the database table and column name for a property on an EF4 entity?如何为 EF4 实体上的属性提取数据库表和列名?
【发布时间】:2011-03-15 14:12:29
【问题描述】:

我正在为使用 EF4 作为数据访问层的应用程序编写审核组件。我能够很容易地确定哪些实体已被修改,并且通过 ObjectStateEntry 对象我可以提取原始值、当前值、实体名称和已修改的属性名称,但我还想提取原始表和SQL Server 中使用的列名(因为它们并不总是与模型的实体和属性名称匹配)

有谁知道这样做的好方法吗?甚至可能吗?映射显然存储在 MSL 中,但我无法找到以编程方式访问这些映射的方法。

【问题讨论】:

    标签: c# sql-server entity-framework entity-framework-4


    【解决方案1】:

    查看实体框架模型设计器后,我看到它使用EdmEntityTypeAttributeDataMemberAttribute 来装饰生成的类和属性。它们每个都有一个Name 属性,其中包含映射实体的名称(分别为表、列)。当属性名称与列名称匹配时,设计器不会为位置参数Name 提供值。 下面的代码对我来说很好。

     private static string GetTableName<T>() where T : EntityObject
        {
            Type type = typeof(T);
            var at = GetAttribute<EdmEntityTypeAttribute>(type);
            return at.Name;
        }
    
        private static string GetColumnName<T>(Expression<Func<T, object>> propertySelector) where T : EntityObject
        {
            Contract.Requires(propertySelector != null, "propertySelector is null.");
    
            PropertyInfo propertyInfo = GetPropertyInfo(propertySelector.Body);
            DataMemberAttribute attribute = GetAttribute<DataMemberAttribute>(propertyInfo);
            if (String.IsNullOrEmpty(attribute.Name))
            {
                return propertyInfo.Name;
            }
            return attribute.Name;
        }
    
        private static T GetAttribute<T>(MemberInfo memberInfo) where T : class
        {
            Contract.Requires(memberInfo != null, "memberInfo is null.");
            Contract.Ensures(Contract.Result<T>() != null);
    
            object[] customAttributes = memberInfo.GetCustomAttributes(typeof(T), false);
            T attribute = customAttributes.Where(a => a is T).First() as T;
            return attribute;
        }
    
        private static PropertyInfo GetPropertyInfo(Expression propertySelector)
        {
            Contract.Requires(propertySelector != null, "propertySelector is null.");
            MemberExpression memberExpression = propertySelector as MemberExpression;
            if (memberExpression == null)
            {
                UnaryExpression unaryExpression = propertySelector as UnaryExpression;
                if (unaryExpression != null && unaryExpression.NodeType == ExpressionType.Convert)
                {
                    memberExpression = unaryExpression.Operand as MemberExpression;
                }
            }
            if (memberExpression != null && memberExpression.Member.MemberType == MemberTypes.Property)
            {
                return memberExpression.Member as PropertyInfo;
            }
            throw new ArgumentException("No property reference was found.", "propertySelector");
        }
    
        // Invocation example
        private static Test()
        {
             string table = GetTableName<User>();
             string column = GetColumnName<User>(u=>u.Name);
        }
    

    【讨论】:

      【解决方案2】:

      所有的模型数据都可以通过这个方法获得 myObjectContext.MetadataWorkspace.GetEntityContainer(myObjectContext.DefaultContainerName, DataSpace.CSSpace);

      这至少应该让你开始了解如何做你想做的事。 DataSpace.CSSpace 指定概念名称和商店名称之间的映射。 DataSpace.CSpace 为您提供概念模型,DataSpace.SSpace 为您提供存储模型。

      【讨论】:

      • 这是正确的假设,但我试过了,它总是抛出异常,即元数据工作区中不存在同名容器。同时我在调试器中看到容器在那里。之前通过执行单独的查询加载了元数据。
      • 第一个参数传入什么?这个块目前是用来为我们获取类型名称的,所以我知道它有效。
      • 我传递了正确的容器名称。我知道它有效。我在CSpace 上使用了很多次,但由于某种原因,当我测试它时,它不适用于CSSpace
      • MetadataWorkspace.GetItemCollection(System.Data.Metadata.Edm.DataSpace.CSSpace, true)[0] 会给你 CSSpace
      • 你可能还想看看OCSpaceOSpaceMetadataWorkspace.GetItemCollection(System.Data.Metadata.Edm.DataSpace.OCSpace, true)EFUtils.DB.MetadataWorkspace.GetItemCollection(System.Data.Metadata.Edm.DataSpace.OSpace, true)
      【解决方案3】:

      如果您编写代码来审核映射,您不是真的在审核/验证 Microsoft 的 EF 代码吗?也许这可以安全地定义在问题域之外,除非审计的目的是建立对 EF 本身的信心。

      但是,如果您确实需要进行此类审核,一种可能性可能是添加构建步骤以将 .edmx 文件作为资源嵌入您正在检查的 DLL 中。你没有说你是否在被测 DLL 上有这种控制/输入。不过,这将是一个 hack —— 正如 JasCav 所说,ORM 的目的是让你尝试的东西变得不必要。

      【讨论】:

      • 我不想审核映射,我想审核实体的更改 - 更改了哪些属性、何时更改以及谁更改了它。我对表名和列名感兴趣的原因是主要开发团队以外的小组需要为应用程序提供生产支持。如果实体名称与表名称不匹配,并且支持团队无权访问代码和/或映射,那么他们的工作就会变得更加困难。
      【解决方案4】:

      这是一个在概念信息和商店信息之间转换的通用算法,用 Visual Basic 2010 编写。

      我编写了一个新例程,将实体/属性对转换为表/列对。此类 MSLMappingAction 在其构造函数中接受模型名称和 XElement XML 树、MSL 映射文件或 XML 字符串。然后使用 ConceptualToStore 方法获取 String 指定实体和属性“表达式”(存储在 MSLConceptualInfo 结构中)并找到表和列名称(存储在 MSLStoreInfo 结构)。

      注意事项:

      1. 也可以编写一个“StoreToConceptual”方法来转换 另一个方向,但 XML 查询可能会有点 更复杂。处理也是如此 导航属性/功能/存储过程映射。
      2. 注意派生实体的继承属性! 如果一个属性是 不特定于派生实体,那么您应该使用基础 实体名称。)

      这是代码。

      主机代码:(对于给出的 XML 示例 [见底部],当给定实体“Location”和属性表达式“Address.Street”[和概念模型名称“SCTModel”]):

      Dim MSL As MSLMappingAction = New MSLMappingAction(".\SCTModel.msl", "SCTModel")
      
      Dim ConceptualInfo As MSLConceptualInfo = New MSLConceptualInfo With {.EntityName = "Location", .PropertyName = "Address.Street"}
      Dim StoreInfo As MSLStoreInfo = MSL.ConceptualToStore(ConceptualInfo)
      MessageBox.Show(StoreInfo.TableName & ": " & StoreInfo.ColumnName)
      

      类代码:

      Option Infer On
      Imports System.Xml.Linq
      
      ''' <summary>
      ''' This class allows one to convert between an EF conceptual model's entity/property pair
      ''' and its database store's table/column pair.
      ''' </summary>
      ''' <remarks>It takes into account entity splitting and complex-property designations;
      ''' it DOES NOT take into account inherited properties
      ''' (in such a case, you should access the entity's base class)</remarks>
      Public Class MSLMappingAction
      
      '   private fields and routines
      Private mmaMSLMapping As XElement
      Private mmaModelName, mmaNamespace As String
      
      Private Function FullElementName(ByVal ElementName As String) As String
      '   pre-pend Namespace to ElementName
      Return "{" & mmaNamespace & "}" & ElementName
      End Function
      
      Private Sub ValidateParams(ByVal MappingXML As XElement, Byval ModelName As String)
      '   verify that model name is specified
      If String.IsNullOrEmpty(ModelName) Then
          Throw New EntityException("Entity model name is not given!")
      End If
      '   verify that we're using C-S space
      If MappingXML.@Space <> "C-S" Then
          Throw New MetadataException("XML is not C-S mapping data!")
      End If
      '   get Namespace and set private variables
      mmaNamespace = MappingXML.@xmlns
      mmaMSLMapping = MappingXML : mmaModelName = ModelName
      End Sub
      
      Private Function IsSequenceEmpty(Items As IEnumerable(Of XElement)) As Boolean
      '   determine if query result is empty
      Return _
          Items Is Nothing OrElse Items.Count = 0
      End Function
      
      '   properties
      ''' <summary>
      ''' Name of conceptual entity model
      ''' </summary>
      ''' <returns>Conceptual-model String</returns>
      ''' <remarks>Model name can only be set in constructor</remarks>
      Public ReadOnly Property EntityModelName() As String
      Get
          Return mmaModelName
      End Get
      End Property
      
      ''' <summary>
      ''' Name of mapping namespace
      ''' </summary>
      ''' <returns>Namespace String of C-S mapping layer</returns>
      ''' <remarks>This value is determined when the XML mapping
      ''' is first parsed in the constructor</remarks>
      Public ReadOnly Property MappingNamespace() As String
      Get
          Return mmaNamespace
      End Get
      End Property
      
      '   constructors
      ''' <summary>
      ''' Get C-S mapping information for an entity model (with XML tree)
      ''' </summary>
      ''' <param name="MappingXML">XML mapping tree</param>
      ''' <param name="ModelName">Conceptual-model name</param>
      ''' <remarks></remarks>
      Public Sub New(ByVal MappingXML As XElement, ByVal ModelName As String)
      ValidateParams(MappingXML, ModelName)
      End Sub
      
      ''' <summary>
      ''' Get C-S mapping information for an entity model (with XML file)
      ''' </summary>
      ''' <param name="MSLFile">MSL mapping file</param>
      ''' <param name="ModelName">Conceptual-model name</param>
      ''' <remarks></remarks>
      Public Sub New(ByVal MSLFile As String, ByVal ModelName As String)
      Dim MappingXML As XElement = XElement.Load(MSLFile)
      ValidateParams(MappingXML, ModelName)
      End Sub
      
      '   methods
      ''' <summary>
      ''' Get C-S mapping infomration for an entity model (with XML String)
      ''' </summary>
      ''' <param name="XMLString">XML mapping String</param>
      ''' <param name="ModelName">Conceptual-model name</param>
      ''' <returns></returns>
      Public Shared Function Parse(ByVal XMLString As String, ByVal ModelName As String)
      Return New MSLMappingAction(XElement.Parse(XMLString), ModelName)
      End Function
      
      ''' <summary>
      ''' Convert conceptual entity/property information into store table/column information
      ''' </summary>
      ''' <param name="ConceptualInfo">Conceptual-model data
      ''' (.EntityName = entity expression String, .PropertyName = property expression String)</param>
      ''' <returns>Store data (.TableName = table-name String, .ColumnName = column-name String)</returns>
      ''' <remarks></remarks>
      Public Function ConceptualToStore(ByVal ConceptualInfo As MSLConceptualInfo) As MSLStoreInfo
      Dim StoreInfo As New MSLStoreInfo
      With ConceptualInfo
          '   prepare to query XML
          If Not .EntityName.Contains(".") Then
              '   make sure entity name is fully qualified
              .EntityName = mmaModelName & "." & .EntityName
          End If
          '   separate property names if there's complex-type nesting
          Dim Properties() As String = .PropertyName.Split(".")
          '   get relevant entity mapping
          Dim MappingInfo As IEnumerable(Of XElement) = _                 
              (From mi In mmaMSLMapping.Descendants(FullElementName("EntityTypeMapping")) _
                  Where mi.@TypeName = "IsTypeOf(" & .EntityName & ")" _
                      OrElse mi.@TypeName = .EntityName _
               Select mi)
          '   make sure entity is in model
          If IsSequenceEmpty(MappingInfo) Then
              Throw New EntityException("Entity """ & .EntityName & """ was not found!")
          End If
          '   get mapping fragments
          Dim MappingFragments As IEnumerable(Of XElement) = _
              (From mf In MappingInfo.Descendants(FullElementName("MappingFragment")) _
               Select mf)
          '   make sure there's at least 1 fragment
          If IsSequenceEmpty(MappingFragments) Then
              Throw New EntityException("Entity """ & .EntityName & """ was not mapped!")
          End If
          '   search each mapping fragment for the desired property
          For Each MappingFragment In MappingFragments
              '   get physical table for this fragment
              StoreInfo.TableName = MappingFragment.@StoreEntitySet
              '   search property expression chain
              Dim PropertyMapping As IEnumerable(Of XElement) = {MappingFragment}
              '   parse complex property info (if any)
              For index = 0 To UBound(Properties) - 1
                  '   go down 1 level
                  Dim ComplexPropertyName = Properties(index)
                  PropertyMapping = _
                      (From pm In PropertyMapping.Elements(FullElementName("ComplexProperty")) _
                          Where pm.@Name = ComplexPropertyName)
                  '   verify that the property specified for this level exists
                  If IsSequenceEmpty(PropertyMapping) Then
                      Exit For 'go to next fragment if not
                  End If
              Next index
              '   property not found? try next fragment
              If IsSequenceEmpty(PropertyMapping) Then
                  Continue For
              End If
              '   parse scalar property info
              Dim ScalarPropertyName = Properties(UBound(Properties))
              Dim ColumnName As String = _
                  (From pm In PropertyMapping.Elements(FullElementName("ScalarProperty")) _
                      Where pm.@Name = ScalarPropertyName _
                      Select CN = pm.@ColumnName).FirstOrDefault
              '   verify that scalar property exists
              If Not String.IsNullOrEmpty(ColumnName) Then
                  '   yes? return (exit) with column info
                  StoreInfo.ColumnName = ColumnName : Return StoreInfo
              End If
          Next MappingFragment
          '   property wasn't found
          Throw New EntityException("Property """ & .PropertyName _
              & """ of entity """ & .EntityName & """ was not found!")
      End With
      End Function
      End Class
      
      ''' <summary>
      ''' Conceptual-model entity and property information  
      ''' </summary>
      Public Structure MSLConceptualInfo
      ''' <summary>
      ''' Name of entity in conceptual model
      ''' </summary>
      ''' <value>Entity expression String</value>
      ''' <remarks>EntityName may or may not be fully qualified (i.e., "ModelName.EntityName");
      ''' when a mapping method is called by the MSLMappingAction class, the conceptual model's
      ''' name and a period will be pre-pended if it's omitted</remarks>
      Public Property EntityName As String
      ''' <summary>
      ''' Name of property in entity
      ''' </summary>
      ''' <value>Property expression String</value>
      ''' <remarks>PropertyName may be either a stand-alone scalar property or a scalar property
      ''' within 1 or more levels of complex-type properties; in the latter case, it MUST be fully
      ''' qualified (i.e., "ComplexPropertyName.InnerComplexPropertyName.ScalarPropertyName")</remarks>
      Public Property PropertyName As String
      End Structure
      
      ''' <summary>
      ''' Database-store table and column information
      ''' </summary>
      Public Structure MSLStoreInfo
      ''' <summary>
      ''' Name of table in database
      ''' </summary>
      Public Property TableName As String
      ''' <summary>
      ''' Name of column in database table
      ''' </summary>
      Public Property ColumnName As String
      End Structure
      

      关键是节点名称都必须有一个命名空间。它把我绊倒了,直到我一次检查我的元素 1!

      这是示例 XML——我从上面代码中的“.\SCTModel.msl”文件加载:

      <?xml version="1.0" encoding="utf-8"?>
      <Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">
        <EntityContainerMapping StorageEntityContainer="SCTModelStoreContainer" CdmEntityContainer="SocialContactsTracker">
          <EntitySetMapping Name="SocialContacts">
            <EntityTypeMapping TypeName="IsTypeOf(SCTModel.SocialContact)">
              <MappingFragment StoreEntitySet="SocialContacts">
                <ScalarProperty Name="Id" ColumnName="Id" />
                <ScalarProperty Name="DateAdded" ColumnName="DateAdded" />
                <ScalarProperty Name="Information" ColumnName="Information" />
                <ComplexProperty Name="DefaultAssociations" TypeName="SCTModel.DefaultAssociations">
                  <ScalarProperty Name="DefaultLocationID" ColumnName="DefaultAssociations_DefaultLocationID" />
                  <ScalarProperty Name="DefaultEmailID" ColumnName="DefaultAssociations_DefaultEmailID" />
                  <ScalarProperty Name="DefaultPhoneNumberID" ColumnName="DefaultAssociations_DefaultPhoneNumberID" />
                  <ScalarProperty Name="DefaultWebsiteID" ColumnName="DefaultAssociations_DefaultWebsiteID" />
                </ComplexProperty>
                <ScalarProperty Name="Picture" ColumnName="Picture" />
              </MappingFragment>
            </EntityTypeMapping>
            <EntityTypeMapping TypeName="IsTypeOf(SCTModel.Person)">
              <MappingFragment StoreEntitySet="SocialContacts_Person">
                <ScalarProperty Name="Id" ColumnName="Id" />
                <ScalarProperty Name="DateOfBirth" ColumnName="DateOfBirth" />
                <ScalarProperty Name="FirstName" ColumnName="FirstName" />
                <ScalarProperty Name="LastName" ColumnName="LastName" />
              </MappingFragment>
            </EntityTypeMapping>
            <EntityTypeMapping TypeName="IsTypeOf(SCTModel.Organization)">
              <MappingFragment StoreEntitySet="SocialContacts_Organization">
                <ScalarProperty Name="Id" ColumnName="Id" />
                <ScalarProperty Name="Name" ColumnName="Name" />
                <ScalarProperty Name="DateOfCreation" ColumnName="DateOfCreation" />
              </MappingFragment>
            </EntityTypeMapping>
          </EntitySetMapping>
          <EntitySetMapping Name="Locations">
            <EntityTypeMapping TypeName="IsTypeOf(SCTModel.Location)">
              <MappingFragment StoreEntitySet="Locations">
                <ScalarProperty Name="Id" ColumnName="Id" />
                <ScalarProperty Name="City" ColumnName="City" />
                <ScalarProperty Name="State" ColumnName="State" />
                <ScalarProperty Name="ZIP" ColumnName="ZIP" />
                <ScalarProperty Name="Country" ColumnName="Country" />
                <ComplexProperty Name="Address" TypeName="SCTModel.Address">
                  <ScalarProperty Name="Street" ColumnName="Address_Street" />
                  <ScalarProperty Name="Apartment" ColumnName="Address_Apartment" />
                  <ScalarProperty Name="HouseNumber" ColumnName="Address_HouseNumber" />
                </ComplexProperty>
              </MappingFragment>
            </EntityTypeMapping>
          </EntitySetMapping>
          <EntitySetMapping Name="PhoneNumbers">
            <EntityTypeMapping TypeName="IsTypeOf(SCTModel.PhoneNumber)">
              <MappingFragment StoreEntitySet="PhoneNumbers">
                <ScalarProperty Name="Id" ColumnName="Id" />
                <ScalarProperty Name="Number" ColumnName="Number" />
                <ScalarProperty Name="PhoneType" ColumnName="PhoneType" />
              </MappingFragment>
            </EntityTypeMapping>
          </EntitySetMapping>
          <EntitySetMapping Name="Emails">
            <EntityTypeMapping TypeName="IsTypeOf(SCTModel.Email)">
              <MappingFragment StoreEntitySet="Emails">
                <ScalarProperty Name="Id" ColumnName="Id" />
                <ScalarProperty Name="DomainName" ColumnName="DomainName" />
                <ScalarProperty Name="UserName" ColumnName="UserName" />
              </MappingFragment>
            </EntityTypeMapping>
          </EntitySetMapping>
          <EntitySetMapping Name="Websites">
            <EntityTypeMapping TypeName="IsTypeOf(SCTModel.Website)">
              <MappingFragment StoreEntitySet="Websites">
                <ScalarProperty Name="Id" ColumnName="Id" />
                <ScalarProperty Name="URL" ColumnName="URL" />
              </MappingFragment>
            </EntityTypeMapping>
          </EntitySetMapping>
          <AssociationSetMapping Name="SocialContactWebsite" TypeName="SCTModel.SocialContactWebsite" StoreEntitySet="SocialContactWebsite">
            <EndProperty Name="SocialContact">
              <ScalarProperty Name="Id" ColumnName="SocialContacts_Id" />
            </EndProperty>
            <EndProperty Name="Website">
              <ScalarProperty Name="Id" ColumnName="Websites_Id" />
            </EndProperty>
          </AssociationSetMapping>
          <AssociationSetMapping Name="SocialContactPhoneNumber" TypeName="SCTModel.SocialContactPhoneNumber" StoreEntitySet="SocialContactPhoneNumber">
            <EndProperty Name="SocialContact">
              <ScalarProperty Name="Id" ColumnName="SocialContacts_Id" />
            </EndProperty>
            <EndProperty Name="PhoneNumber">
              <ScalarProperty Name="Id" ColumnName="PhoneNumbers_Id" />
            </EndProperty>
          </AssociationSetMapping>
          <AssociationSetMapping Name="SocialContactEmail" TypeName="SCTModel.SocialContactEmail" StoreEntitySet="SocialContactEmail">
            <EndProperty Name="SocialContact">
              <ScalarProperty Name="Id" ColumnName="SocialContacts_Id" />
            </EndProperty>
            <EndProperty Name="Email">
              <ScalarProperty Name="Id" ColumnName="Emails_Id" />
            </EndProperty>
          </AssociationSetMapping>
          <AssociationSetMapping Name="SocialContactLocation" TypeName="SCTModel.SocialContactLocation" StoreEntitySet="SocialContactLocation">
            <EndProperty Name="SocialContact">
              <ScalarProperty Name="Id" ColumnName="SocialContacts_Id" />
            </EndProperty>
            <EndProperty Name="Location">
              <ScalarProperty Name="Id" ColumnName="Locations_Id" />
            </EndProperty>
          </AssociationSetMapping>
        </EntityContainerMapping>
      </Mapping>
      

      需要注意的是,上述代码仅在 MSL 文件的 XML 存储为单独的文件时(例如当元数据由宿主项目复制到输出路径顶部时)或 XML 已经可用时才有效。我仍然需要知道的是,当 MSL 信息仅作为程序集资源存储时,如何提取它。

      对于那些想要提供更多意见的人,请记住,任何通用解决方案都应适用于任何版本的 .NET(至少 4.0 及更高版本)——包括 4.7 之前的版本。它还应该能够处理复杂的属性表达式。

      【讨论】:

        【解决方案5】:

        我有点困惑,为什么 SQL Server 中使用的原始表名和列名与模型的实体名和属性名不匹配。除了用于提供多对多映射的表外,(通常)您的对象名称/属性与表名和列名之间应该有直接对应关系。

        话虽如此,实体框架是一个 ORM。该框架的全部目的是为您的数据库提供面向对象的视图,并抽象出必须直接与关系数据库交互。 EF 并不是真的要让你绕过框架,据我所知,你想要做的事情是不可能的。 (但是,如果我错了,这是我今天学到的新东西,我会相应地删除或编辑这个答案。)

        【讨论】:

        • 实体中的属性与数据库列的名称不同是很常见的。例如,C# 与 SQL 有不同的命名约定。我们的一个数据库有[2_CODE_A2] 之类的列,但该属性被简单地命名为Code
        • @Ladislav - 啊……好吧。我没有想到这一点。 (在我使用 EF 时,数据库列名称与对象名称相匹配,必要时减去名称的复数形式。)无论哪种情况,我相信我的评论的第二部分仍然有效。
        • 我们正在针对遗留数据库构建 EF4 组件,该数据库的表名和列名意义不大,因此@Ladislav 的示例适用于我们的情况。我们不仅希望记录/审核对实体的更改,还希望跟踪源表和列名,以使 DBA(他们不了解代码)更容易提供生产支持。 @JasCav 您是否确定 EF4 不会公开此信息,或者您只是根据其他 ORM 猜测?
        • @mrmcderm - 我承认我不是实体框架方面的专家。但是,据我所知,我不相信你可以做你想做的事。我注意到您在上面的评论中说您要审核对实体的更改。最终,对实体的更改与数据库相关联。为什么不直接审计数据库呢? (您的应用程序通过实体工作,但您真正关心的是数据。)也许我对您要完成的工作感到困惑。对此我深表歉意。
        猜你喜欢
        • 2014-08-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多