procedimiento - mostrar datos de una tabla sql en c#
¿Cómo puedo extraer la tabla de la base de datos y el nombre de columna para una propiedad en una entidad EF4? (5)
Estoy escribiendo un componente de auditoría para una aplicación que usa EF4 para la capa de acceso a datos. Soy capaz de determinar muy fácilmente qué entidades se han modificado y, a través del objeto ObjectStateEntry, puedo extraer los valores originales, los valores actuales, el nombre de la entidad y los nombres de las propiedades que se modificaron, pero también me gustaría extraer la tabla sin procesar y y nombres de columna utilizados en SQL Server (ya que no siempre coinciden con los nombres de entidad y propiedad del modelo)
¿Alguien sabe de una buena manera de hacer esto? ¿Es posible? Las asignaciones están obviamente almacenadas en el MSL, pero no puedo encontrar una forma de acceder mediante programación a esas asignaciones.
Estoy un poco confundido por qué los nombres de columna y tabla sin formato utilizados en SQL Server no coinciden con los nombres de entidad y propiedad del modelo. Excepto por la tabla utilizada para proporcionar el mapeo muchos-a-muchos, allí (normalmente) debería haber una correspondencia directa entre los nombres / propiedades de los objetos y los nombres de las tablas y columnas.
Dicho esto, Entity Framework es un ORM. El propósito total del marco es proporcionar una vista orientada a objetos a su base de datos y abstracción, sin tener que interactuar directamente con la base de datos relacional. EF no tiene la intención de permitirle sortear el marco y, hasta donde yo sé, lo que está buscando hacer no es posible. (Sin embargo, si me equivoco, es algo nuevo que habrá aprendido hoy y borraré o editaré esta respuesta en consecuencia).
Si escribe código para auditar la asignación, ¿no está realmente auditando / verificando el código EF de Microsoft? Quizás esto se pueda definir de manera segura fuera del dominio del problema, a menos que el objetivo de la auditoría sea generar confianza en el EF mismo.
Pero si realmente necesita hacer ese tipo de auditoría, una posibilidad podría ser agregar un paso de compilación para incrustar el archivo .edmx como un recurso en la DLL que está examinando. No dijo si tiene ese tipo de control / entrada en la DLL bajo prueba. Sin embargo, sería un truco, como dijo JasCav, el propósito de los ORM es hacer que lo que intentas sea innecesario.
Todos los datos del modelo están disponibles a través de estos métodos myObjectContext.MetadataWorkspace.GetEntityContainer(myObjectContext.DefaultContainerName, DataSpace.CSSpace);
eso debería al menos darle un comienzo sobre cómo hacer lo que quiere. DataSpace.CSSpace
especifica la asignación entre los nombres conceptuales y los nombres de la tienda. DataSpace.CSpace
le brinda el Modelo conceptual y DataSpace.SSpace
le brinda el modelo de almacenamiento.
Después de echar un vistazo al diseñador de modelos de framework de entidad, vi que usa EdmEntityTypeAttribute
y DataMemberAttribute
para decorar clases y propiedades generadas. Cada uno de ellos tiene una propiedad Name
que contiene el nombre de la entidad mapeada (tabla, columna respectivamente). Cuando el nombre de la propiedad coincide con el nombre de la columna, el diseñador no proporciona un valor para el argumento posicional Name
. El código a continuación funciona bien para mí.
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);
}
Aquí hay un algoritmo de propósito general para convertir información conceptual y de tienda, escrita en Visual Basic 2010.
He escrito una nueva rutina para convertir un par entidad / propiedad en un par tabla / columna. Esta clase, MSLMappingAction , toma en su constructor el nombre del modelo y un árbol XElement XML, un archivo de mapeo MSL o una cadena XML. Luego, se utiliza el método ConceptualToStore para tomar String que especifica entidades y propiedades "expresiones" (almacenadas en la estructura MSLConceptualInfo ) y encontrar los nombres de tabla y columna (almacenados en la estructura MSLStoreInfo ).
Notas:
- También se podría escribir un método " StoreToConceptual " para convertir en la otra dirección, pero las consultas XML probablemente serían un poco más complejas. Lo mismo ocurre con el manejo de las correspondencias navegación-propiedad / función / procedimiento almacenado.
- ¡Cuidado con las propiedades heredadas de las entidades derivadas! Si una propiedad no es específica de la entidad derivada, entonces debe usar el nombre de la entidad base.)
Aquí está el código.
Código de host: (Para la muestra XML dada [ver abajo], devuelve el nombre de la tabla "Ubicaciones" y el nombre de columna "Dirección_Calle" para la información de la tienda cuando se le da a la entidad "Ubicación" y la expresión de propiedad "Dirección.Calle" [y nombre del modelo conceptual "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)
Código de clase:
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
El problema es que los nombres de los nodos deben tener un espacio de nombres antes de ellos. Me hizo tropezar hasta que verifiqué mis elementos 1 a la vez.
Aquí está el XML de muestra , que cargo desde el archivo ". / SCTModel.msl" en el código anterior:
<?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>