簡體   English   中英

如何為 EF4 實體上的屬性提取數據庫表和列名稱?

[英]How can I extract the database table and column name for a property on an EF4 entity?

我正在為使用 EF4 作為數據訪問層的應用程序編寫審計組件。 我能夠很容易地確定哪些實體已被修改,並且通過 ObjectStateEntry 對象,我可以提取被修改的原始值、當前值、實體名稱和屬性名稱,但我還想提取原始表和和 SQL Server 中使用的列名(因為它們並不總是與模型的實體和屬性名稱匹配)

有誰知道這樣做的好方法嗎? 甚至有可能嗎? 映射顯然存儲在 MSL 中,但我無法找到以編程方式訪問這些映射的方法。

在查看實體框架模型設計器后,我看到它使用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);
    }

所有模型數據都可以通過此方法獲得myObjectContext.MetadataWorkspace.GetEntityContainer(myObjectContext.DefaultContainerName, DataSpace.CSSpace);

這至少應該讓你開始了解如何做你想做的事。 DataSpace.CSSpace指定概念名稱和商店名稱之間的映射。 DataSpace.CSpace為您提供概念模型, DataSpace.SSpace為您提供存儲模型。

這是一個在概念和存儲信息之間轉換的通用算法,用 Visual Basic 2010 編寫。

我編寫了一個新例程,用於將實體/屬性對轉換為表/列對。 此類MSLMappingAction在其構造函數中接受模型名稱和 XElement XML 樹、MSL 映射文件或 XML 字符串。 然后使用ConceptualToStore方法獲取指定實體和屬性“表達式”的字符串(存儲在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!

這是我從上面代碼中的“.\\SCTModel.msl”文件加載的示例XML:

<?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 之前的版本。 它還應該能夠處理復雜的屬性表達式。

如果您編寫代碼來審核映射,您真的不是在審核/驗證 Microsoft 的 EF 代碼嗎? 也許這可以安全地定義在問題域之外,除非審計的目的是建立對 EF 本身的信心。

但是,如果您確實需要進行這種審核,一種可能性可能是添加一個構建步驟,將 .edmx 文件作為資源嵌入您正在檢查的 DLL 中。 你沒有說你是否在被測 DLL 上有那種控制/輸入。 不過,這將是一種黑客行為——正如 JasCav 所說,ORM 的目的是使您嘗試的內容變得不必要。

我有點困惑為什么 SQL Server 中使用的原始表名和列名與模型的實體和屬性名稱不匹配。 除了用於提供多對多映射的表外,(通常)您的對象名稱/屬性與表名稱和列名稱之間應該有直接對應關系。

話雖如此,實體框架是一個 ORM。 該框架的全部目的是為您的數據庫提供面向對象的視圖,並抽象出必須直接與關系數據庫交互。 EF 並不是真的要讓你繞過框架,據我所知,你想要做的事情是不可能的。 (但是,如果我錯了,這是我今天學到的新東西,我將相應地刪除或編輯此答案。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM