Get Database Table Name from Entity Framework MetaData

Viewed 75853

I'm trying to figure out a way to get the underlying SQL table name for a given entity type. I've experimented around with the MetadataWorkspace queries and while I can get lots of information from the object or the storage space, I can't seem to figure out how to map between the two.

So say I have a type in the object model called Lookup - how do I find the tablename (wws_lookups) in the database?

I can query all the EntityType objects for CSpace and SSpace and I can see both listed correctly but I can't figure out how to get SSpace from CSpace.

Is there any way to do this?

25 Answers

I use Nigel's approach (extracting table name from .ToTraceString()) but with some modifications, because his code won't work if the table is not in the default SQL Server schema (dbo.{table-name}).

I've created extension methods for DbContext and ObjectContext objects:

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;
    }
}

More details here:
Entity Framework: Get mapped table name from an entity

Most of the answers here don't work with derived classes. This one does. And gives you the schema too. I combined the answers here and improved on it a little (by taking out things like First() and Single() and converting them to things like Where() and SelectMany() and returning the schema name).

This works with 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;
    }

Adding another answer for the special case when you use annotations to explicitly tell EF which table name to use. For example, if you have:

[Table("tblCompany")]
public class Company
{
}

You can easily access the TableAttribute annotation to find the table name:

((System.ComponentModel.DataAnnotations.Schema.TableAttribute)typeof(YourNamespace.BO.Company).GetCustomAttribute(typeof(System.ComponentModel.DataAnnotations.Schema.TableAttribute))).Name

which is tblCompany for the given sample.

As ready-to-use method:

using System.Reflection;
using System.ComponentModel.DataAnnotations.Schema;

public static string GetTableName<T>() {
    return ((TableAttribute)typeof(T).GetCustomAttribute(typeof(TableAttribute))).Name;
}

(I am aware that this will not help the OP but given the title of the question, people may end up here who may be looking for this answer.)

No, unfortunately it is impossible using the Metadata APIs to get to the tablename for a given entity.

This is because the Mapping metadata is not public, so there is no way to go from C-Space to S-Space using the EF's APIs.

If you really need to do this you could always build the map yourself by parsing the MSL. This is not for the faint of heart, but it should be possible, unless you are using QueryViews (which are incredibly rare), at which point it is for all intents and purposes impossible (you would have to parse ESQL... argh!)

Alex James

Microsoft.

Using EF Core with SQL server it's as easy as:

_context.Model.FindEntityType(YOUR_VAR_TYPE).SqlServer().TableName

In versions 3.0 and 3.1 it's rather trivial:

var myClassTableName = _context.Model.FindEntityType(typeof(MyClass)).GetTableName();

A possible workaround (not great, but neither are the alternatives...):

var sql = Context.EntitySetName.ToTraceString();

...then parse the SQL, which should be quite simple.

Here is another way to find the table name. It is a bit odd but works. 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

Here is a version assuming you have context and have a selected entity in memory that you need to find the real table name for.

    
    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.");
        } 
    }

In the case of using Entity Framework Core 2.0+, you can easily get the relational metadata using provided APIs.

foreach (var entityType in dbContext.Model.GetEntityTypes())
{
    var tableName = entityType.Relational().TableName;
    foreach (var propertyType in entityType.GetProperties())
    {
        var columnName = propertyType.Relational().ColumnName;
    }
}

You need to have Microsoft.EntityFrameworkCore.Relational Nuget package installed to be able to use Relational() method.

If you want just a quick and dirty way to get the table name (and you don't need it in your application's code), you can look at the XML generated for your Model.edmx. Find your entity in the edmx:Mappings section and the line: MappingFragment StoreEntitySet="YourTableName" will give you the table's actual name.

Alex is right - this is a sad limitation in the Metadata API. I have to just load the MSL as an XML document and do lookups of S-space entities as I process my C-space model.

You can use extension method to get Table name from your entity class
In this case, You wont need DbContext to get table name , it gets it inside extension method

public static string GetTableName<T>(this T entity) where T : class
{
    var object_context = GetObjectContext(entity);

    if (object_context == null || object_context.TransactionHandler == null)
        return null;

     var dbcontext=object_context.TransactionHandler.DbContext;
    var query= dbcontext.Set(entity.GetType()).ToString();
    var reg = new Regex(@"FROM \[dbo\]\.\[(?<table>.*)\] AS");
    var match = reg.Match(query);

    var tableName= match.Groups["table"].Value;
    return tableName;
}

private static ObjectContext GetObjectContext(object entity)
{
    var field = entity.GetType().GetField("_entityWrapper");

    if (field == null)
        return null;

    var val= field.GetValue(entity);
    var property = val.GetType().GetProperty("Context");
    var context = (ObjectContext)property.GetValue(val, null);

    return context;
}

This is where we reach to DbContext:

var object_context = GetObjectContext(entity)

Usage:

var tableName = entity.GetTableName();
Related