Obtenga el nombre de la tabla de la base de datos de Entity Framework MetaData

Estoy tratando de encontrar la forma de obtener el nombre de la tabla SQL subyacente para un tipo de entidad dado. He experimentado con las consultas de MetadataWorkspace y aunque puedo obtener mucha información del objeto o el espacio de almacenamiento, parece que no puedo encontrar la forma de mapear entre los dos.

Entonces, supongamos que tengo un tipo en el modelo de objetos llamado Búsqueda: ¿cómo puedo encontrar el nombre de la tabla (wws_lookups) en la base de datos?

Puedo consultar todos los objetos EntityType para CSpace y SSpace y puedo ver ambos listados correctamente, pero no puedo entender cómo obtener SSpace desde CSpace.

¿Hay alguna manera de hacer esto?

Uso el enfoque de Nigel (extrayendo el nombre de la tabla de .ToTraceString() ) pero con algunas modificaciones, porque su código no funcionará si la tabla no está en el esquema predeterminado de SQL Server ( dbo.{table-name} ).

DbContext métodos de extensión para objetos DbContext y ObjectContext :

 public static class ContextExtensions { public static string GetTableName(this DbContext context) where T : class { ObjectContext objectContext = ((IObjectContextAdapter) context).ObjectContext; return objectContext.GetTableName(); } public static string GetTableName(this ObjectContext context) where T : class { string sql = context.CreateObjectSet().ToTraceString(); Regex regex = new Regex(@"FROM\s+(?.+)\s+AS"); Match match = regex.Match(sql); string table = match.Groups["table"].Value; return table; } }

Más detalles aquí:
Marco de la entidad: obtener el nombre de la tabla asignada de una entidad

EDITAR Esta respuesta ahora obsoleta debido a la nueva función en EF 6.1: mapeo entre tipos de tablas . Ve allí primero!

Tuve un problema con las otras respuestas porque tengo un tipo derivado. Obtuve este método (dentro de mi clase de contexto) para trabajar – Tengo solo una capa de herencia en mi modelo en este momento

 private readonly static Dictionary _mappingCache = new Dictionary(); private ObjectContext _ObjectContext { get { return (this as IObjectContextAdapter).ObjectContext; } } private EntitySetBase GetEntitySet(Type type) { if (_mappingCache.ContainsKey(type)) return _mappingCache[type]; type = GetObjectType(type); string baseTypeName = type.BaseType.Name; string typeName = type.Name; ObjectContext octx = _ObjectContext; var es = octx.MetadataWorkspace .GetItemCollection(DataSpace.SSpace) .GetItems() .SelectMany(c => c.BaseEntitySets .Where(e => e.Name == typeName || e.Name == baseTypeName)) .FirstOrDefault(); if (es == null) throw new ArgumentException("Entity type not found in GetEntitySet", typeName); // Put es in cache. _mappingCache.Add(type, es); return es; } internal String GetTableName(Type type) { EntitySetBase es = GetEntitySet(type); //if you are using EF6 return String.Format("[{0}].[{1}]", es.Schema, es.Table); //if you have a version prior to EF6 //return string.Format( "[{0}].[{1}]", // es.MetadataProperties["Schema"].Value, // es.MetadataProperties["Table"].Value ); } internal Type GetObjectType(Type type) { return System.Data.Entity.Core.Objects.ObjectContext.GetObjectType(type); } 

NB: hay planes para mejorar la Metadata API y, si esto no está obteniendo lo que queremos, entonces podemos ver el Primer Código de EF Mapeo entre Tipos y Tablas

No, lamentablemente es imposible usar las API de metadatos para obtener el nombre de tabla para una entidad determinada.

Esto se debe a que los metadatos de asignación no son públicos, por lo que no hay forma de pasar de C-Space a S-Space utilizando las API de EF.

Si realmente necesita hacer esto, siempre puede construir el mapa usted mismo analizando el MSL. Esto no es para corazones débiles, pero debería ser posible, a menos que esté usando QueryViews (que son increíblemente raros), en cuyo punto es imposible para todos los propósitos (tendría que analizar ESQL … argh! )

Alex James

Microsoft.

Hay una manera de borrar datos usando EF sin tener que cargarlo primero. Lo describí en un poco más detenido en: http://nigelfindlater.blogspot.com/2010/04/how-to-delete-objects-in-ef4 -without.html

El truco es convertir el IQueriable en un ObjectQuery y usar el método ToTraceString. Luego edite la cadena sql resultante. Funciona, pero debe tener cuidado porque está pasando por alto los mecanismos que EF tiene en su lugar para mantener las dependencias y las limitaciones. Pero por razones de rendimiento, creo que está bien hacer esto …

que te diviertas…

Nigel …

  private string GetClause(IQueryable clause) where TEntity : class { string snippet = "FROM [dbo].["; string sql = ((ObjectQuery)clause).ToTraceString(); string sqlFirstPart = sql.Substring(sql.IndexOf(snippet)); sqlFirstPart = sqlFirstPart.Replace("AS [Extent1]", ""); sqlFirstPart = sqlFirstPart.Replace("[Extent1].", ""); return sqlFirstPart; } public void DeleteAll(IQueryable clause) where TEntity : class { string sqlClause = GetClause(clause); this.context.ExecuteStoreCommand(string.Format(CultureInfo.InvariantCulture, "DELETE {0}", sqlClause)); } 

Si está utilizando la plantilla T4 para las clases POCO, puede obtenerla alterando la Plantilla T4. Ver fragmento:

 <# //////////////////////////////////////////////////////////////////////////////// region.Begin("Custom Properties"); string xPath = "//*[@TypeName='" + entity.FullName + "']"; XmlDocument doc = new XmlDocument(); doc.Load(inputFile); XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); nsmgr.AddNamespace("edmx", "http://schemas.microsoft.com/ado/2008/10/edmx"); XmlNode item; XmlElement root = doc.DocumentElement; item = root.SelectSingleNode(xPath); #> //<#= xPath #> //<#= entity.FullName #> //<#= (item == null).ToString() #> <# if (item != null) #> // Table Name from database public string TableName { get { return "<#= item.ChildNodes[0].Attributes["StoreEntitySet"].Value #>"; } } <# region.End(); //////////////////////////////////////////////////////////////////////////////// 

Si está haciendo codefirst en EF6, puede agregar algo como lo siguiente a su clase dbcontext.

  public string GetTableName(Type entityType) { var sql = Set(entityType).ToString(); var regex = new Regex(@"FROM \[dbo\]\.\[(?.*)\] AS"); var match = regex.Match(sql); return match.Groups["table"].Value; }

Una posible solución alternativa (no excelente, pero tampoco lo son las alternativas …):

 var sql = Context.EntitySetName.ToTraceString(); 

… luego analizar el SQL, que debería ser bastante simple.

Esto es lo que pude encontrar usando LINQ to XML. El código también obtiene las asignaciones para los nombres de las columnas.

 var d = XDocument.Load("MyModel.edmx"); XNamespace n = "http://schemas.microsoft.com/ado/2008/09/mapping/cs"; var l = (from etm in d.Descendants() where etm.Name == n + "EntityTypeMapping" let s = etm.Attribute("TypeName").Value select new { Name = s.Remove(0, s.IndexOf(".") + 1).Replace(")", ""), Table = etm.Element(n + "MappingFragment").Attribute("StoreEntitySet").Value, Properties = (from sp in etm.Descendants(n + "ScalarProperty") select new { Name = sp.Attribute("Name").Value, Column = sp.Attribute("ColumnName").Value }).ToArray() }).ToArray(); 

Una mejor manera es usar StoreItemCollection de Metadata. Este tipo ya ha proporcionado un ejemplo de uso: obtener tablas y relaciones

EF 6.1, código primero:

 public static string GetTableName(this DbContext context) where T : class { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; return objectContext.GetTableName(typeof(T)); } public static string GetTableName(this DbContext context, Type t) { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; return objectContext.GetTableName(t); } private static readonly Dictionary TableNames = new Dictionary(); public static string GetTableName(this ObjectContext context, Type t) { string result; if (!TableNames.TryGetValue(t, out result)) { lock (TableNames) { if (!TableNames.TryGetValue(t, out result)) { string entityName = t.Name; ReadOnlyCollection storageMetadata = context.MetadataWorkspace.GetItems(DataSpace.CSSpace); foreach (EntityContainerMapping ecm in storageMetadata) { EntitySet entitySet; if (ecm.StoreEntityContainer.TryGetEntitySetByName(entityName, true, out entitySet)) { if (String.IsNullOrEmpty(entitySet.Schema)) { result = entitySet.Table; break; } //we must recognize if we are under SQL Server Compact version, which does not support multiple schemas //SQL server compact does not support schemas, entity framework sets entitySet.Schema set to "dbo", anyway //the System.Data.Entity.Infrastructure.TableExistenceChecker.GetTableName() returns only table name //schema is (not) added by the overrides of the method AnyModelTableExistsInDatabase //the SqlCeTableExistenceChecker has the knowledge that there is no metadata schema needed //the SqlTableExistenceChecker has the knowledge that there is metadata with schema, which should be added to the table names var entityConnection = (System.Data.Entity.Core.EntityClient.EntityConnection) context.Connection; DbConnection storeConnection = entityConnection.StoreConnection; if (storeConnection != null && "SqlCeConnection".Equals(storeConnection.GetType().Name, StringComparison.OrdinalIgnoreCase)) { result = entitySet.Table; break; } result = entitySet.Schema + "." + entitySet.Table; break; } } TableNames.Add(t,result); } } } return result; } 

Aquí hay otra forma de encontrar el nombre de la tabla. Es un poco extraño pero funciona. VB:

 For Each Table In northwind.MetadataWorkspace.GetItemCollection(New System.Data.Metadata.Edm.DataSpace) 'adds table name to a list of strings all table names in EF have the project namespace in front of it.' If Table.ToString.Contains("namespace of project") then 'using substring to remove project namespace from the table name.' TableNames.Add(Table.ToString.Substring("length of namespace name")) End If Next 

Puede intentar la extensión MappingAPI: https://efmappingapi.codeplex.com/

Es realmente fácil de usar

 context.Db().TableName 

Aquí hay una versión suponiendo que tiene contexto y tiene una entidad seleccionada en la memoria para la que necesita encontrar el nombre real de la tabla.

    
     clase estática pública ObjectContextExtentions
     {
         cadena estática pública TableNameFor (este contexto ObjectContext, entrada ObjectStateEntry)
         {
             var generic =
                 context.GetType (). GetProperties (). ToList (). Primero (p => p.Name == entry.EntityKey.EntitySetName);
             var objectset = generic.GetValue (context, null);

             var method = objectset.GetType (). GetMethod ("ToTraceString");
             var sql = (String) method.Invoke (objectset, null);

             var match = Regex.Match (sql, @ "DESDE \ s + \ [dbo \] \. \ [(?  [^ \]] +) \]", RegexOptions.Multiline);
             if (match.Success)
             {
                 return match.Groups ["TableName"]. Value;
             }

             lanzar una nueva ArgumentException ("No se puede encontrar el nombre de la tabla.");
         } 
     }

En realidad, he pasado por el mismo problema y he producido un fragmento de código abstracto que le da dos Dictionary> ($ table_name, $ columns_name_list). El primero tiene lista de bases de datos + lista de nombres de columnas, el segundo tiene entidades EF locales + propiedades

Por supuesto, puede agregar más controles en función del tipo de datos, por cierto, que lo obligaría a escribir un código increíblemente complicado.

P & L

PD: Perdón por el estilo comprimido, soy un fanático de la lambda

 using (EFModelContext efmc = new EFModelContext("appConfigConnectionName")) { string schemaName = "dbo"; string sql = @"select o.name + '.' + c.name from sys.all_objects o inner join sys.schemas s on s.schema_id = o.schema_id inner join sys.all_columns c on c.object_id = o.object_id where Rtrim(Ltrim(o.type)) in ('U') and s.name = @p0"; Dictionary> dbTableColumns = new Dictionary>(); efmc.Database.SqlQuery(sql, schemaName).Select(tc => { string[] splitted = System.Text.RegularExpressions.Regex.Split(tc, "[.]"); return new { TableName = splitted[0], ColumnName = splitted[1] }; }).GroupBy(k => k.TableName, k => k.ColumnName).ToList().ForEach(ig => dbTableColumns.Add(ig.Key, ig.ToList())); Dictionary> efTableColumns = new Dictionary>(); efTableColumns = ((IObjectContextAdapter)uc).ObjectContext.MetadataWorkspace .GetItems(DataSpace.SSpace).OfType() .ToDictionary( eft => eft.MetadataProperties .First(mp => mp.Name == "TableName").Value.ToString(), eft => eft.Properties.Select(p => p.Name).ToList()); } 

Alex tiene razón: esta es una limitación triste en la API de metadatos. Tengo que cargar el MSL como un documento XML y hacer búsquedas de entidades de S-Space mientras proceso mi modelo de C-espacio.

Usando EF5 y un poquito de reflexión, algo como lo siguiente debería hacer el truco:

 using System; using System.Collections; using System.Data.Entity.Infrastructure; using System.Data.Metadata.Edm; using System.Linq; using System.Reflection; namespace EFHelpers { public class EFMetadataMappingHelper { public static string GetTableName(MetadataWorkspace metadata, DbEntityEntry entry) { var entityType = entry.Entity.GetType(); var objectType = getObjectType(metadata, entityType); var conceptualSet = getConceptualSet(metadata, objectType); var storeSet = getStoreSet(metadata, conceptualSet); var tableName = findTableName(storeSet); return tableName; } private static EntitySet getStoreSet(MetadataWorkspace metadata, EntitySetBase entitySet) { var csSpace = metadata.GetItems(DataSpace.CSSpace).Single(); var flags = BindingFlags.NonPublic | BindingFlags.Instance; var entitySetMaps = (ICollection)csSpace.GetType().GetProperty("EntitySetMaps", flags).GetValue(csSpace, null); object mapping = null; foreach (var map in entitySetMaps) { var set = map.GetType().GetProperty("Set", flags).GetValue(map, null); if (entitySet == set) { mapping = map; break; } } var m_typeMappings = ((ICollection)mapping.GetType().BaseType.GetField("m_typeMappings", flags).GetValue(mapping)).OfType().Single(); var m_fragments = ((ICollection)m_typeMappings.GetType().BaseType.GetField("m_fragments", flags).GetValue(m_typeMappings)).OfType().Single(); var storeSet = (EntitySet) m_fragments.GetType().GetProperty("TableSet", flags).GetValue(m_fragments, null); return storeSet; } private static string findTableName(EntitySet storeSet) { string tableName = null; MetadataProperty tableProperty; storeSet.MetadataProperties.TryGetValue("Table", true, out tableProperty); if (tableProperty == null || tableProperty.Value == null) storeSet.MetadataProperties.TryGetValue("http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator:Table", true, out tableProperty); if (tableProperty != null) tableName = tableProperty.Value as string; if (tableName == null) tableName = storeSet.Name; return tableName; } private static EntityType getObjectType(MetadataWorkspace metadata, Type entityType) { var objectItemCollection = (ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace); var edmEntityType = metadata .GetItems(DataSpace.OSpace) .First(e => objectItemCollection.GetClrType(e) == entityType); return edmEntityType; } private static EntitySetBase getConceptualSet(MetadataWorkspace metadata, EntityType entityType) { var entitySetBase = metadata .GetItems(DataSpace.CSpace) .SelectMany(a => a.BaseEntitySets) .Where(s => s.ElementType.Name == entityType.Name) .FirstOrDefault(); return entitySetBase; } } } 

Llamarlo es así:

 public string GetTableName(DbContext db, DbEntityEntry entry) { var metadata = ((IObjectContextAdapter)db).ObjectContext.MetadataWorkspace; return EFMetadataMappingHelper.GetTableName(metadata, entry); } 

Copiando mi respuesta a otra pregunta aquí.

Si alguien sigue mirando, así es como lo hice. Este es un método de extensión para DBContext que toma un tipo y devuelve los nombres de las columnas físicas y sus propiedades.

Esto utiliza el contexto del objeto para obtener la lista de columnas físicas, luego utiliza la propiedad de metadatos “NombrePreferido” para asignar cada columna a su propiedad.

Como utiliza el contexto del objeto, inicia una conexión a la base de datos, por lo que la primera ejecución será lenta dependiendo de la complejidad del contexto.

 public static IDictionary GetTableColumns(this DbContext ctx, Type entityType) { ObjectContext octx = (ctx as IObjectContextAdapter).ObjectContext; EntityType storageEntityType = octx.MetadataWorkspace.GetItems(DataSpace.SSpace) .Where(x => x.BuiltInTypeKind == BuiltInTypeKind.EntityType).OfType() .Single(x => x.Name == entityType.Name); var columnNames = storageEntityType.Properties.ToDictionary(x => x.Name, y => y.MetadataProperties.FirstOrDefault(x => x.Name == "PreferredName")?.Value as string ?? y.Name); return storageEntityType.Properties.Select((elm, index) => new {elm.Name, Property = entityType.GetProperty(columnNames[elm.Name])}) .ToDictionary(x => x.Name, x => x.Property); } 

Para usarlo, simplemente crea una clase estática auxiliar y agrega la función anterior; entonces es tan simple como llamar

 var tabCols = context.GetTableColumns(typeof(EntityType)); 

Para EF6, mezclar / comprimir el código de otras respuestas aquí y alrededor (VB, lo siento):

  Public Function getDBTableName(data As myDataModel, ByVal entity As Object) As String Dim context = CType(data, IObjectContextAdapter).ObjectContext Dim sName As String = entity.GetType.BaseType.Name 'use BaseType to avoid proxy names' Dim map = context.MetadataWorkspace.GetItems(Of EntityContainerMapping)(DataSpace.CSSpace).FirstOrDefault Return (From esmap In map.EntitySetMappings Select esmap.EntityTypeMappings.First( Function(etm) etm.EntityType.Name = sName ).Fragments.First.StoreEntitySet.Name).FirstOrDefault 'TODO: use less .first everywhere but filter the correct ones' End Function 

Funciona para db-first.
Relativamente fácil de entender siguiendo un archivo .edmx.