[英]How can I extract the database table and column name for a property on an EF4 entity?
我正在為使用 EF4 作為數據訪問層的應用程序編寫審計組件。 我能夠很容易地確定哪些實體已被修改,並且通過 ObjectStateEntry 對象,我可以提取被修改的原始值、當前值、實體名稱和屬性名稱,但我還想提取原始表和和 SQL Server 中使用的列名(因為它們並不總是與模型的實體和屬性名稱匹配)
有誰知道這樣做的好方法嗎? 甚至有可能嗎? 映射顯然存儲在 MSL 中,但我無法找到以編程方式訪問這些映射的方法。
在查看實體框架模型設計器后,我看到它使用EdmEntityTypeAttribute
和DataMemberAttribute
來裝飾生成的類和屬性。 它們每個都有一個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結構中)。
筆記:
這是代碼。
主機代碼:(對於給定的 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.