Obtenir le nom de la Table de la base de données des métadonnées du cadre de L'entité
j'essaie de trouver un moyen d'obtenir le nom de table SQL sous-jacent pour un type d'entité donné. J'ai fait des expériences avec les requêtes MetadataWorkspace et bien que je puisse obtenir beaucoup d'informations de l'objet ou de l'espace de stockage, je n'arrive pas à comprendre comment faire la carte entre les deux.
donc j'ai un type dans le modèle d'objet appelé Lookup - comment trouver le nom de table (wws_lookups) dans la base de données?
je peux interroger tous les Objets EntityType pour CSpace et SSpace et je peux voir les deux listés correctement mais je ne peux pas comprendre comment obtenir SSpace à partir de CSpace.
y a-t-il un moyen de faire ça?
19 réponses
j'utilise L'approche de Nigel (extraire le nom de la table de .ToTraceString()
) mais avec quelques modifications, parce que son code ne fonctionnera pas si la table n'est pas dans le schéma de serveur SQL par défaut ( dbo.{table-name}
).
j'ai créé des méthodes d'extension pour DbContext
et ObjectContext
objets:
public static class ContextExtensions
{
public static string GetTableName<T>(this DbContext context) where T : class
{
ObjectContext objectContext = ((IObjectContextAdapter) context).ObjectContext;
return objectContext.GetTableName<T>();
}
public static string GetTableName<T>(this ObjectContext context) where T : class
{
string sql = context.CreateObjectSet<T>().ToTraceString();
Regex regex = new Regex(@"FROM\s+(?<table>.+)\s+AS");
Match match = regex.Match(sql);
string table = match.Groups["table"].Value;
return table;
}
}
plus de détails ici:
Entity Framework: obtenir le nom de la table mappée d'une entité
EDIT Cette réponse est désormais obsolète en raison de la nouvelle caractéristique de L'EF 6.1 : correspondance entre les types de table . Vas-y en premier!
j'ai eu un problème avec les autres réponses, car j'ai un type dérivé. J'ai obtenu cette méthode (dans ma classe de contexte) pour travailler - je n'ai qu'une couche d'héritage dans mon modèle en ce moment
private readonly static Dictionary<Type, EntitySetBase> _mappingCache
= new Dictionary<Type, EntitySetBase>();
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<EntityContainer>()
.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 il y a des plans pour améliorer L'API de métadonnées et si ce N'est pas obtenir ce que nous voulons alors nous pouvons regarder code EF première correspondance entre les Types et les Tables
non, malheureusement il est impossible d'utiliser les API métadonnées pour accéder au nom de table d'une entité donnée.
c'est parce que les métadonnées de cartographie ne sont pas publiques, il n'y a donc pas moyen de passer de L'Espace C à L'Espace S en utilisant les API de L'EF.
Si vous vraiment besoin pour ce faire, vous pouvez toujours construire la carte vous-même par l'analyse de la MSL. Ce n'est pas pour les faibles de cœur, mais il devrait être possible, sauf si vous utilisez QueryViews (qui sont incroyablement rares), à quel point il est à toutes fins pratiques impossible (vous auriez à analyser ESQL... argh!)
Alex James
Microsoft.
il y a un moyen de supprimer des données en utilisant EF sans avoir à les charger d'abord je les ai décrites dans un peu plus de retenir dans: http://nigelfindlater.blogspot.com/2010/04/how-to-delete-objects-in-ef4-without.html
le truc est de lancer le IQueriable dans un ObjectQuery et d'utiliser la méthode de traçage. Puis modifiez la chaîne sql résultante. Cela fonctionne, mais vous devez être prudent parce que vous contournez les mécanismes que EF a en place pour maintien de la dépendance et des contraintes. Mais pour des raisons de performance, je pense que c'est bien de faire ça....
amusez-vous bien...
Nigel...
private string GetClause<TEntity>(IQueryable<TEntity> clause) where TEntity : class
{
string snippet = "FROM [dbo].[";
string sql = ((ObjectQuery<TEntity>)clause).ToTraceString();
string sqlFirstPart = sql.Substring(sql.IndexOf(snippet));
sqlFirstPart = sqlFirstPart.Replace("AS [Extent1]", "");
sqlFirstPart = sqlFirstPart.Replace("[Extent1].", "");
return sqlFirstPart;
}
public void DeleteAll<TEntity>(IQueryable<TEntity> clause) where TEntity : class
{
string sqlClause = GetClause<TEntity>(clause);
this.context.ExecuteStoreCommand(string.Format(CultureInfo.InvariantCulture, "DELETE {0}", sqlClause));
}
si vous utilisez le gabarit T4 pour les catégories de la POCO, vous pouvez l'obtenir en modifiant le gabarit T4. Voir l'extrait suivant:
<#
////////////////////////////////////////////////////////////////////////////////
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 vous faites codefirst dans EF6, vous pouvez simplement ajouter quelque chose comme ceci à votre classe dbcontext.
public string GetTableName(Type entityType)
{
var sql = Set(entityType).ToString();
var regex = new Regex(@"FROM \[dbo\]\.\[(?<table>.*)\] AS");
var match = regex.Match(sql);
return match.Groups["table"].Value;
}
un contournement possible (pas grand, mais ni l'un ni l'autre ne sont les alternatives...):
var sql = Context.EntitySetName.ToTraceString();
...puis analysez le SQL, qui devrait être assez simple.
voici ce que J'ai pu trouver en utilisant LINQ à XML. Le code obtient les correspondances pour les noms de colonne aussi bien.
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();
une meilleure façon est d'utiliser la collection Storeitem à partir des métadonnées. Ce mec a déjà fourni un exemple d'utilisation: Obtenir des Tables et des Relations
EF 6.1, code-first:
public static string GetTableName<T>(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<Type,string> TableNames = new Dictionary<Type, string>();
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<EntityContainerMapping> storageMetadata = context.MetadataWorkspace.GetItems<EntityContainerMapping>(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;
}
Voici une autre façon de trouver le nom de la table. C'est un peu bizarre mais qui fonctionne. 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
vous pouvez essayer mappingapi extension: https://efmappingapi.codeplex.com /
Il est vraiment facile à utiliser
context.Db<YourEntityType>().TableName
Voici une version supposant que vous avez un contexte et avez une entité sélectionnée en mémoire pour laquelle vous avez besoin de trouver le vrai nom de table.
public static class ObjectContextExtentions { public static string TableNameFor(this ObjectContext context, ObjectStateEntry entry) { var generic = context.GetType().GetProperties().ToList().First(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, @"FROM\s+\[dbo\]\.\[(?<TableName>[^\]]+)\]", RegexOptions.Multiline); if (match.Success) { return match.Groups["TableName"].Value; } throw new ArgumentException("Unable to find Table name."); } }
en fait j'ai traversé le même problème, et j'ai produit un extrait de code abstrait qui vous donne deux Dictionary<string,List<string>>
($table_name,$columns_name_list).
Le premier a une Table de base de données + liste de noms de colonne, le second a des entités locales EF + propriétés
bien sûr, vous pouvez ajouter plus de contrôles contre le type de données, btw imho qui vous forcerait à écrire du code incroyablement compliqué.
P&L
P. S. désolé pour la compression du style, je suis un lambda fanatique
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<string, List<string>> dbTableColumns = new Dictionary<string, List<string>>();
efmc.Database.SqlQuery<string>(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<string, List<string>> efTableColumns = new Dictionary<string, List<string>>();
efTableColumns = ((IObjectContextAdapter)uc).ObjectContext.MetadataWorkspace
.GetItems(DataSpace.SSpace).OfType<EntityType>()
.ToDictionary( eft => eft.MetadataProperties
.First(mp => mp.Name == "TableName").Value.ToString(),
eft => eft.Properties.Select(p => p.Name).ToList());
}
Alex a raison - c'est une limitation triste dans L'API de métadonnées. Je dois simplement charger le MSL comme un document XML et faire des recherches d'entités s-space pendant que je traite mon modèle C-space.
en utilisant EF5 et un peu de réflexion, quelque chose comme ce qui suit devrait faire l'affaire:
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<object>().Single();
var m_fragments = ((ICollection)m_typeMappings.GetType().BaseType.GetField("m_fragments", flags).GetValue(m_typeMappings)).OfType<object>().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<EntityType>(DataSpace.OSpace)
.First(e => objectItemCollection.GetClrType(e) == entityType);
return edmEntityType;
}
private static EntitySetBase getConceptualSet(MetadataWorkspace metadata, EntityType entityType) {
var entitySetBase = metadata
.GetItems<EntityContainer>(DataSpace.CSpace)
.SelectMany(a => a.BaseEntitySets)
.Where(s => s.ElementType.Name == entityType.Name)
.FirstOrDefault();
return entitySetBase;
}
}
}
appelez ça comme ça:
public string GetTableName(DbContext db, DbEntityEntry entry) {
var metadata = ((IObjectContextAdapter)db).ObjectContext.MetadataWorkspace;
return EFMetadataMappingHelper.GetTableName(metadata, entry);
}
copie de ma réponse à une autre question ici.
si quelqu'un regarde toujours, Voici comment je l'ai fait. C'est une méthode d'extension pour le DBContext qui prend un type et renvoie les noms de colonnes physiques et leurs propriétés.
utilise le contexte objet pour obtenir la liste des colonnes physiques, puis utilise la propriété de métadonnées" PreferredName " pour mapper chaque colonne de sa propriété.
Puisqu'il utilise le contexte objet, il initie un connexion à la base de données, donc la première exécution sera lente en fonction de la complexité du contexte.
public static IDictionary<String, PropertyInfo> 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<EntityType>()
.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);
}
pour l'utiliser, il suffit de créer une classe helper statique, et ajouter la fonction ci-dessus; alors c'est aussi simple que d'appeler
var tabCols = context.GetTableColumns(typeof(EntityType));
pour EF6, mélange / compression de code d'autres réponses ici et autour (VB, je suis désolé):
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
ça marche pour db-first.
Relativement facile à comprendre en un .fichier edmx.
la Plupart des réponses ici ne fonctionnent pas avec les classes dérivées. Ce ne. Et vous donne le schéma de trop. J'ai combiné les réponses ici et l'ai légèrement améliorée (en enlevant des choses comme First () et Single () et en les convertissant en choses comme Where () et SelectMany () et en retournant le nom de schéma).
cela fonctionne avec EF 6.1+
// This can return multiple values because it is possible to have one entity correspond to multiple tables when doing entity splitting.
public static IEnumerable<string> GetTableName<T>(this DbContext context)
{
var type = typeof(T);
var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
// Get the part of the model that contains info about the actual CLR types
var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));
// Get the entity type from the model that maps to the CLR type
var entityType = metadata
.GetItems<EntityType>(DataSpace.OSpace)
.Single(e => objectItemCollection.GetClrType(e) == type);
// Get the entity set that uses this entity type
var entitySet = metadata.GetItems(DataSpace.CSpace).Where(x => x.BuiltInTypeKind == BuiltInTypeKind.EntityType).Cast<EntityType>().Single(x => x.Name == entityType.Name);
// Find the mapping between conceptual and storage model for this entity set
var entitySetMappings = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace).Single().EntitySetMappings.ToList();
// Find the storage entity sets (tables) that the entity is mapped
//EntitySet table;
var fragments = new List<MappingFragment>();
var mappings = entitySetMappings.Where(x => x.EntitySet.Name == entitySet.Name);
//if (mappings.Count() > 0)
//return mappings.SelectMany(m => m.EntityTypeMappings.SelectMany(em => em.Fragments)).ToList();
fragments.AddRange(mappings.SelectMany(m => m.EntityTypeMappings.SelectMany(em => em.Fragments)));
fragments.AddRange(entitySetMappings.Where(x => x.EntityTypeMappings.Where(y => y.EntityType != null).Any(y => y.EntityType.Name == entitySet.Name))
.SelectMany(m => m.EntityTypeMappings.Where(x => x.EntityType != null && x.EntityType.Name == entityType.Name).SelectMany(x => x.Fragments)));
//if (mapping != null)
//return mapping.EntityTypeMappings.Where(x => x.EntityType != null).Single(x => x.EntityType.Name == entityType.Name).Fragments;
fragments.AddRange(entitySetMappings.Where(x => x.EntityTypeMappings.Any(y => y.IsOfEntityTypes.Any(z => z.Name == entitySet.Name)))
.SelectMany(m => m.EntityTypeMappings.Where(x => x.IsOfEntityTypes.Any(y => y.Name == entitySet.Name)).SelectMany(x => x.Fragments)));
//var fragments = getFragments();
// Return the table name from the storage entity set
var tableNames = fragments.Select(f =>
{
var schemaName = f.StoreEntitySet.Schema;
var tableName = (string)f.StoreEntitySet.MetadataProperties["Table"].Value ?? f.StoreEntitySet.Name;
var name = $"[{schemaName}].[{tableName}]";
return name;
}).Distinct().ToList();
return tableNames;
}