Reputation: 3969
Suppose I have this table:
How can I get the column name and database datatype from DbContext
in Entity Framework Core?
Tips
The column with name clg# converted to clg1 by EF Core Scaffold tool so I need real column name not current EF name
I need database type, not clrType, of course the must be cross platform. Maybe I will change the database so the method must work too.
Desired result:
<D.clg#, int>
<D.clgname, nvarchar(50)>
<D.city, nvarchar(50)>
<D.pname, nvarchar(50)>
Can anyone provide a solution ?
Upvotes: 26
Views: 43965
Reputation: 1296
For EF Core 5, you need to use the overload that accepts a StoreObjectIdentifier: GetColumnName(IProperty, StoreObjectIdentifier).
Update for EF 5:
var entityType = dbContext.Model.FindEntityType(clrEntityType);
var schema = entityType.GetSchema();
var tableName = entityType.GetTableName();
var storeObjectIdentifier = StoreObjectIdentifier.Table(tableName, schema);
var columnName = entityType.FindProperty(propertyName).GetColumnName(storeObjectIdentifier);
Upvotes: 15
Reputation: 1164
Here is how I get column names in EF 6.
public IEnumerable<string> GetColumnNames<T>() where T : class
{
var storeObjectIdentifier = StoreObjectIdentifier.Table(Set<T>().EntityType.GetTableName(), Set<T>().EntityType.GetSchema());
var columns = Set<T>().EntityType
.GetProperties()
.Select(x => x.GetColumnName(storeObjectIdentifier))
.ToList();
return columns;
}
Upvotes: 0
Reputation: 53
I created and used this function to do the exact thing on EF Core 5.0
public static List<ColumnInfo> GetAllColumns(this DbContext db, Type TableName)
{
var entityType = db.Model.FindEntityType(TableName);
var properties = entityType.GetProperties();
List<ColumnInfo> columns = new List<ColumnInfo>(properties.Count());
foreach (var property in properties)
columns.Add(new ColumnInfo(property.GetColumnName(StoreObjectIdentifier.SqlQuery(entityType)), property.GetColumnType()));
return columns;
}
public class ColumnInfo
{
public string Name;
public string Type;
public ColumnInfo(string Name, string Type)
{
this.Name = Name;
this.Type = Type;
}
}
you can also replace string Type
to SqlDbType Type
and so, the foreach line would be like this:
columns.Add(new ColumnInfo(
property.GetColumnName(StoreObjectIdentifier.SqlQuery(entityType)),
Enum.Parse<SqlDbType>( property.GetColumnType().Slice(0,"(",true), true)));
where Slice
is my function, there's a necessary piece of code:
public static string Slice(this string s, int Start, string EndsWith, bool AlwaysReturnString = false)
{
var end =s.LastIndexOf(EndsWith);
if (end < 0) return AlwaysReturnString? s : null;
if (Start > end) throw new ArgumentException($"start ({Start}) is be bigger than end ({end})");
return s.Slice(Start, end);
}
public static int IndexOfEnd(this string s, string s2)
{
if (s == null)
if (s2.Length == 0)
return 0;
int i = s.IndexOf(s2);
return i == -1 ? -1 : i + s2.Length;
}
public static string Slice(this string s, int Start = 0, int End = Int32.MaxValue)
{
if (Start < 0) throw new ArgumentOutOfRangeException($"Start is {Start}");
if (Start > End) throw new ArgumentException($"start ({Start}) is be bigger than end ({End})");
if (End > s.Length) End = s.Length;
return s.Substring(Start, End - Start);
}
Upvotes: 1
Reputation: 493
Based on EFC3.1 answer I have created this helper to store all table names and column names into an dictionaries of a singleton populated on first use so the code doesn't have to traverse everything again and again. We use it for NPGSQL bulk copy operations and it seems to work properly. This version does not filter out any properties from entity classes so be careful about ordering of fields when doing column name lists/strings. But then again as I understand it, you will get only properties that are mapped in context so everything might be ok.
The helper
public class ContextHelper
{
private readonly ILogger<ContextHelper> logger;
private readonly ApplicationDbContext context;
private static Dictionary<Type, string> tableNames = new Dictionary<Type, string>(30);
private Dictionary<Type, Dictionary<string, string>> columnNames = new Dictionary<Type, Dictionary<string, string>>(30);
public ContextHelper(ILogger<ContextHelper> logger, ApplicationDbContext context)
{
this.logger = logger;
this.context = context;
PopulateTableNames();
PopulateColumnNames();
}
private void PopulateTableNames()
{
logger.LogInformation("Populating table names in context helper");
foreach (var entityType in context.Model.GetEntityTypes())
{
tableNames.Add(entityType.ClrType, entityType.GetTableName());
}
}
private void PopulateColumnNames()
{
logger.LogInformation("Populating column names in context helper");
foreach (var entityType in context.Model.GetEntityTypes())
{
var clrType = entityType.ClrType;
if (!columnNames.ContainsKey(clrType))
{
columnNames.Add(clrType, new Dictionary<string, string>(30));
}
foreach (var property in entityType.GetProperties())
{
columnNames[clrType].Add(property.Name, property.GetColumnName());
}
}
}
public string GetTableName<T>()
{
return context.Model.FindEntityType(typeof(T)).GetTableName();
}
public string GetColumnName<T>(string propertyName)
{
return columnNames[typeof(T)][propertyName];
}
public List<string> GetColumnNames<T>()
{
return columnNames[typeof(T)].Select(x => x.Value).ToList();
}
}
Startup registration
services.AddSingleton<ContextHelper>();
Usage, something along these lines
var columnNames = contextHelper.GetColumnNames<OvenEventLog>().Where(x=>x != contextHelper.GetColumnName<OvenEventLog>(nameof(OvenEventLog.IdLog)));
var separatedCN = string.Join(", ", columnNames);
using (var writer = conn.BeginBinaryImport(
$"COPY {contextHelper.GetTableName<OvenEventLog>()} ({separatedCN}) FROM STDIN (FORMAT BINARY)")
Upvotes: 2
Reputation: 718
For those who arrived here, but are not using .NET CORE, like me. try this:
public partial class MyDbContext : System.Data.Entity.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;
}
public string[] GetColumnName(Type entityType)
{
var strs = new List<string>();
var sql = Set(entityType).ToString();
var regex = new Regex(@"\[Extent1\]\.\[(?<columnName>.*)\] AS");
var matches = regex.Matches(sql);
foreach (Match item in matches)
{
var name = item.Groups["columnName"].Value;
strs.Add(name);
}
return strs.ToArray();
}
}
maybe redundante, but it saves time.
antonio
Upvotes: 1
Reputation: 205589
Update (EF Core 3.x): Starting with EF Core 3.0, the metadata API has changed again - Relational()
extensions have been removed, and properties have been replaced with Get
and Set
extension methods, so now the code looks like this:
var entityType = dbContext.Model.FindEntityType(clrEntityType);
// Table info
var tableName = entityType.GetTableName();
var tableSchema = entityType.GetSchema();
// Column info
foreach (var property in entityType.GetProperties())
{
var columnName = property.GetColumnName();
var columnType = property.GetColumnType();
};
Update (EF Core 2.x): Starting with EF Core 2.0, the things have changed, so the original answer does not apply anymore. Now EF Core builds separate model for each database type, so the code is much simpler and uses directly the Relational()
extensions:
var entityType = dbContext.Model.FindEntityType(clrEntityType);
// Table info
var tableName = entityType.Relational().TableName;
var tableSchema = entityType.Relational().Schema;
// Column info
foreach (var property in entityType.GetProperties())
{
var columnName = property.Relational().ColumnName;
var columnType = property.Relational().ColumnType;
};
Original answer (EF Core 1.x):
Getting the access to the associated metadata is much easier in EF Core compared to EF - you start from DbContext.Model
property to get IModel
, use GetEntityTypes
or FindEntityType
to get IEntityType
, then GetProperties
or FindProperty
to get IProperty
etc.
However the problem is that EF Core allows you to use different setting fro different target database. In order to get the attributes corresponding to the current database used by the context, you need to get access to the IRelationalDatabaseProviderServices
and use AnnotationProvider
and TypeMapper
properties to get the information needed.
Here is an example:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Storage;
public class DbColumnInfo
{
public string Name;
public string Type;
}
public static class RelationalDbHelpers
{
public static IEnumerable<DbColumnInfo> GetDbColums(this DbContext dbContext, Type clrEntityType)
{
var dbServices = dbContext.GetService<IDbContextServices>();
var relationalDbServices = dbServices.DatabaseProviderServices as IRelationalDatabaseProviderServices;
var annotationProvider = relationalDbServices.AnnotationProvider;
var typeMapper = relationalDbServices.TypeMapper;
var entityType = dbContext.Model.FindEntityType(clrEntityType);
// Not needed here, just an example
var tableMap = annotationProvider.For(entityType);
var tableName = tableMap.TableName;
var tableSchema = tableMap.Schema;
return from property in entityType.GetProperties()
let columnMap = annotationProvider.For(property)
let columnTypeMap = typeMapper.FindMapping(property)
select new DbColumnInfo
{
Name = columnMap.ColumnName,
Type = columnTypeMap.StoreType
};
}
}
Upvotes: 59