我正在爲使用EF4作爲數據訪問層的應用程序編寫一個審計組件。我能夠非常容易地確定哪些實體已被修改,並且通過ObjectStateEntry對象可以提取已修改的原始值,當前值,實體名稱和屬性名稱,但我也想提取原始表並在SQL Server使用的列名(因爲他們並不總是符合模型的實體和屬性名)如何提取EF4實體上的屬性的數據庫表和列名稱?
有誰知道的一個很好的辦法做到這一點?它甚至有可能嗎?映射顯然存儲在MSL中,但我無法找到一種以編程方式訪問這些映射的方法。
我正在爲使用EF4作爲數據訪問層的應用程序編寫一個審計組件。我能夠非常容易地確定哪些實體已被修改,並且通過ObjectStateEntry對象可以提取已修改的原始值,當前值,實體名稱和屬性名稱,但我也想提取原始表並在SQL Server使用的列名(因爲他們並不總是符合模型的實體和屬性名)如何提取EF4實體上的屬性的數據庫表和列名稱?
有誰知道的一個很好的辦法做到這一點?它甚至有可能嗎?映射顯然存儲在MSL中,但我無法找到一種以編程方式訪問這些映射的方法。
我有點困惑,爲什麼在SQL Server中使用的原料表和列名不匹配模型的實體和屬性名。除用於提供多對多映射的表格之外,(通常)應該是對象名稱/屬性與表格名稱和列名稱之間的直接對應關係。
隨着中說,實體框架是一個ORM。框架的整個目的是爲數據庫提供一個面向對象的視圖,並將其抽象爲直接與關係數據庫進行交互。 EF並非真正意味着讓你繞開框架,而且據我所知,你期望做的事是不可能的。 (不過,如果我錯了,這是新的東西,我會今天獲悉,我會刪除或相應地編輯此答案。)
如果你寫的代碼審覈的映射,是不是你真正的審計/驗證微軟EF代碼?也許這可以安全地從問題領域界定出來,除非審計的目的是建立對EF本身的信心。
但如果你真的需要做這種審覈的,一種可能性是增加一個構建步驟嵌入.edmx文件在你檢查DLL的資源。你沒有說你是否對被測試的DLL有這樣的控制/輸入。然而,這將是一次黑客攻擊 - 正如JasCav所說,ORM的目的是爲了完成你想要的東西。
我不想審覈映射,我想審覈對實體的更改 - 什麼屬性發生了變化,何時發生變化以及誰更改。我對錶和列名稱感興趣的原因是因爲除主開發團隊之外的其他組需要爲應用程序提供生產支持。如果實體名稱與表名不匹配,並且支持團隊無法訪問代碼和/或映射,那麼這使得他們的工作變得更加困難。 – mrmcderm 2011-03-17 13:56:10
所有模型數據,可通過此方法 myObjectContext.MetadataWorkspace.GetEntityContainer(myObjectContext.DefaultContainerName, DataSpace.CSSpace);
認爲至少應該給你如何做你想做的一個開始。 DataSpace.CSSpace
指定概念名稱和商店名稱之間的映射。 DataSpace.CSpace
爲您提供了概念模型,DataSpace.SSpace
爲您提供了存儲模型。
這是正確的假設,但我試過了,它總是拋出異常,具有名稱的容器不存在於元數據工作區中。同時,我在調試器中看到容器在那裏。通過執行單獨的查詢來加載元數據。 – 2011-03-17 14:08:16
你通過什麼傳遞第一個參數?此塊用於獲取我們目前的類型的名稱,所以我知道它的工作原理。 – 2011-03-17 14:23:35
我正在傳遞正確的容器名稱。我知道它的工作原理。我用'CSpace'多次使用它,但是由於某種原因,當我測試它時,它不能與'CSSpace'一起工作。 – 2011-03-17 14:26:39
在看了一個實體框架模型設計器之後,我看到它使用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);
}
這裏有一個概念和存儲的信息之間進行轉換的通用算法,用Visual Basic 2010
我寫了一個新的常規,爲實體/屬性對轉換成一個表/列對。這個類,MSLMappingAction,需要在其構造模型名稱以及一個的XElement XML樹,一個MSL映射文件,或XML字符串。然後使用ConceptualToStore方法取String指定實體和屬性「表達式」(存儲在MSLConceptualInfo結構中)並查找表名和列名(存儲在MSLStoreInfo結構中)。
注:
下面的代碼。
主機代碼:(對於給定的XML樣本[見底],它給出的實體「位置」和屬性表達式時返回表名稱「位置」和列名「Address_Street」的店鋪信息「地址.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 [email protected] <> "C-S" Then
Throw New MetadataException("XML is not C-S mapping data!")
End If
' get Namespace and set private variables
mmaNamespace = [email protected]
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 [email protected] = "IsTypeOf(" & .EntityName & ")" _
OrElse [email protected] = .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 = [email protected]
' 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 [email protected] = 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 [email protected] = ScalarPropertyName _
Select CN = [email protected]).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>
這是很常見的,在實體屬性沒有相同的名稱作爲數據庫列。例如C#有不同的命名約定,然後是SQL。我們的一個數據庫有像[[2_CODE_A2]]這樣的列,但是這個propery只是簡單的命名爲Code。 – 2011-03-15 15:36:37
@拉迪斯拉夫 - 啊...好的。我沒有想過這個。 (在我使用的EF,數據庫列名匹配的對象名稱,減去必要時名字的複數。)在任何情況下,我的意見的第二部分仍然有效,我相信。 – JasCav 2011-03-15 16:16:27
我們正在建立針對具有小於有意義的表和列名的傳統數據庫的EF4組件,因此@拉吉斯拉夫的例子適用於我們的情況。我們想不僅記錄/審計變爲實體,而且還跟蹤源表和列名,使由數據庫管理員(不具有可視性代碼誰)更容易生產的支持。 @JasCav你確定知道EF4沒有公開這些信息,或者你只是猜測其他ORM? – mrmcderm 2011-03-17 13:52:52