2011-03-15 68 views
5

我正在为使用EF4作为数据访问层的应用程序编写一个审计组件。我能够非常容易地确定哪些实体已被修改,并且通过ObjectStateEntry对象可以提取已修改的原始值,当前值,实体名称和属性名称,但我也想提取原始表并在SQL Server使用的列名(因为他们并不总是符合模型的实体和属性名)如何提取EF4实体上的属性的数据库表和列名称?

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

回答

-1

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

随着中说,实体框架是一个ORM。框架的整个目的是为数据库提供一个面向对象的视图,并将其抽象为直接与关系数据库进行交互。 EF并非真正意味着让你绕开框架,而且据我所知,你期望做的事是不可能的。 (不过,如果我错了,这是新的东西,我会今天获悉,我会删除或相应地编辑此答案。)

+1

这是很常见的,在实体属性没有相同的名称作为数据库列。例如C#有不同的命名约定,然后是SQL。我们的一个数据库有像[[2_CODE_A2]]这样的列,但是这个propery只是简单的命名为Code。 – 2011-03-15 15:36:37

+0

@拉迪斯拉夫 - 啊...好的。我没有想过这个。 (在我使用的EF,数据库列名匹配的对象名称,减去必要时名字的复数。)在任何情况下,我的意见的第二部分仍然有效,我相信。 – JasCav 2011-03-15 16:16:27

+0

我们正在建立针对具有小于有意义的表和列名的传统数据库的EF4组件,因此@拉吉斯拉夫的例子适用于我们的情况。我们想不仅记录/审计变为实体,而且还跟踪源表和列名,使由数据库管理员(不具有可视性代码谁)更容易生产的支持。 @JasCav你确定知道EF4没有公开这些信息,或者你只是猜测其他ORM? – mrmcderm 2011-03-17 13:52:52

0

如果你写的代码审核的映射,是不是你真正的审计/验证微软EF代码?也许这可以安全地从问题领域界定出来,除非审计的目的是建立对EF本身的信心。

但如果你真的需要做这种审核的,一种可能性是增加一个构建步骤嵌入.edmx文件在你检查DLL的资源。你没有说你是否对被测试的DLL有这样的控制/输入。然而,这将是一次黑客攻击 - 正如JasCav所说,ORM的目的是为了完成你想要的东西。

+0

我不想审核映射,我想审核对实体的更改 - 什么属性发生了变化,何时发生变化以及谁更改。我对表和列名称感兴趣的原因是因为除主开发团队之外的其他组需要为应用程序提供生产支持。如果实体名称与表名不匹配,并且支持团队无法访问代码和/或映射,那么这使得他们的工作变得更加困难。 – mrmcderm 2011-03-17 13:56:10

2

所有模型数据,可通过此方法 myObjectContext.MetadataWorkspace.GetEntityContainer(myObjectContext.DefaultContainerName, DataSpace.CSSpace);

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

+0

这是正确的假设,但我试过了,它总是抛出异常,具有名称的容器不存在于元数据工作区中。同时,我在调试器中看到容器在那里。通过执行单独的查询来加载元数据。 – 2011-03-17 14:08:16

+0

你通过什么传递第一个参数?此块用于获取我们目前的类型的名称,所以我知道它的工作原理。 – 2011-03-17 14:23:35

+0

我正在传递正确的容器名称。我知道它的工作原理。我用'CSpace'多次使用它,但是由于某种原因,当我测试它时,它不能与'CSSpace'一起工作。 – 2011-03-17 14:26:39

2

在看了一个实体框架模型设计器之后,我看到它使用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); 
    } 
0

这里有一个概念和存储的信息之间进行转换的通用算法,用Visual Basic 2010

我写了一个新的常规,为实体/属性对转换成一个表/列对。这个类,MSLMappingAction,需要在其构造模型名称以及一个的XElement XML树,一个MSL映射文件,或XML字符串。然后使用ConceptualToStore方法取String指定实体和属性“表达式”(存储在MSLConceptualInfo结构中)并查找表名和列名(存储在MSLStoreInfo结构中)。

注:

  1. 你也可以写一个“StoreToConceptual”方法在 转换其他方向,但XML查询可能会有点 更加复杂。这同样适用于处理 导航属性格式/功能/存储过程映射。
  2. 当心继承派生实体的属性!如果属性 不特定的衍生实体,那么你应该使用基本 实体的名称。)

下面的代码。

主机代码:(对于给定的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> 
相关问题