Как я могу извлечь таблицу базы данных и имя столбца для свойства объекта EF4?

Я пишу аудиторский компонент для приложения, которое использует EF4 для уровня доступа к данным. Я могу очень легко определить, какие объекты были изменены, и через объект ObjectStateEntry я могу извлечь исходные значения, текущие значения, имена сущностей и имена свойств, которые были изменены, но я также хотел бы извлечь исходную таблицу и и имена столбцов, используемые в SQL Server (поскольку они не всегда совпадают с именами объектов и свойств модели)

Кто-нибудь знает хороший способ сделать это? Возможно ли это? Очевидно, что сопоставления хранятся в MSL, но я не могу определить способ программного доступа к этим сопоставлениям.

Все данные модели доступны с помощью этих методов myObjectContext.MetadataWorkspace.GetEntityContainer(myObjectContext.DefaultContainerName, DataSpace.CSSpace);

это должно по крайней мере дать вам начало о том, как делать то, что вы хотите. DataSpace.CSSpace определяет сопоставление между именами DataSpace.CSSpace и именами Store. DataSpace.CSpace предоставляет концептуальную модель, а DataSpace.SSpace предоставляет вам модель хранения.

После заглядывания в конструктор модели структуры сущности я увидел, что он использует 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); } 

Если вы пишете код для аудита сопоставления, разве вы действительно не проверяете / проверяете EF-код Microsoft? Возможно, это можно безопасно определить из проблемной области, если целью аудита не является уверенность в самом EF.

Но если вам действительно нужно провести такой аудит, одна из возможностей может заключаться в добавлении шага сборки для вставки файла .edmx в качестве ресурса в DLL, которую вы изучаете. Вы не сказали, если у вас есть такой контроль / ввод в тестируемой DLL. Это было бы взломом, хотя, как сказал JasCav, цель ORM – сделать именно то, что вы пытаетесь сделать ненужным.

Вот универсальный алгоритм для преобразования между концептуальной и хранимой информацией, написанной на Visual Basic 2010.

Я написал новую процедуру, чтобы преобразовать пару entity / property в пару table / column. Этот класс, MSLMappingAction , принимает в своем конструкторе имя модели и XML-дерево XElement, файл сопоставления MSL или строку XML. Затем используется метод ConceptualToStore для принятия String, определяющего сущности и свойства «выражения» (хранящиеся в структуре MSLConceptualInfo ), и найдите имена таблиц и столбцов (хранящихся в структуре MSLStoreInfo ).

Заметки:

  1. Можно также написать метод « StoreToConceptual » для преобразования в другом направлении, но XML-запросы, вероятно, будут немного сложнее. То же самое относится и к обработке сопоставлений навигации / функции / хранимых процедур.
  2. Остерегайтесь унаследованных свойств производных объектов! Если свойство не является специфическим для производного объекта, то вы должны использовать имя базового объекта.)

Вот код.

Код хоста: (Для образца XML, указанного [см. Внизу], он возвращает имя таблицы «Местоположения» и имя столбца «Адрес_Стрит» для информации о магазине, если предоставляется сущность «Местоположение» и выражение свойства «Адрес.Стрит» [и название концептуальной модели "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 CS space If [email protected] <> "CS" Then Throw New MetadataException("XML is not CS 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 CS 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 CS 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 CS 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 CS 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 (ie, "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 (ie, "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="CS" 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> 

Я немного смущен, почему исходные имена таблиц и столбцов, используемые в SQL Server, не соответствуют именам объектов и свойств модели. За исключением таблицы, используемой для обеспечения сопоставления «многие ко многим», там (как правило) должно быть прямое соответствие между вашими именами / свойствами объекта и именами таблиц и именами столбцов.

С учетом сказанного, Entity Framework является ORM. Вся цель этой структуры – предоставить объектно-ориентированное представление вашей базе данных и абстрагироваться от необходимости напрямую взаимодействовать с реляционной базой данных. EF на самом деле не предназначен для того, чтобы позволить вам обходить структуру и, насколько я знаю, то, что вы хотите сделать, невозможно. (Однако, если я ошибаюсь, это то, что я узнаю сегодня, и я удалю или отредактирую этот ответ соответственно.)

  • Что лучше: ограничение или FK? Для столбца состояния
  • Как использовать Entity Framework 4 для системных представлений SQL Azure?
  • Почему мастер модели данных Entity не создает объект, отражающий таблицу db со значением по умолчанию getdate ()?
  • Проблема структуры Entity: как обрабатывать столбец с ошибкой
  • Ленивая загрузка с функцией импорта
  • Отображение типа CLR типа EDM неоднозначно с EF 6 и 5?
  • EF4 как переключить схему (например, dbo -> custId), чтобы идентичные таблицы хранились в нескольких схемах
  • Запрос Linq не работает, пока sql работает правильно
  • ошибка при вставке в таблицу вместо триггера из структуры данных сущности
  • Вставка INSTEAD OF INSERT вызывает ошибку в SaveChanges of Entity Framework
  • Вкладыши становятся все более медленными с течением времени?
  • Давайте будем гением компьютера.