Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get Column name and corresponding Database Type from DbContext in Entity Framework Core

Tags:

Suppose I have this table:

enter image description here

How can I get the column name and database datatype from DbContext in Entity Framework Core?

Tips

  1. The column with name clg# converted to clg1 by EF Core Scaffold tool so I need real column name not current EF name

  2. 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 ?

like image 827
HamedFathi Avatar asked Feb 25 '17 11:02

HamedFathi


People also ask

How do I get column names in Entity Framework?

FindEntityType(clrEntityType); // Table info var tableName = entityType. GetTableName(); var tableSchema = entityType. GetSchema(); // Column info foreach (var property in entityType. GetProperties()) { var columnName = property.

How do I get specific columns in Entity Framework?

In Entity Framework Core we can select specific columns by using either anonymous types or DTOs.

What is the difference between DbContext and DbSet?

Intuitively, a DbContext corresponds to your database (or a collection of tables and views in your database) whereas a DbSet corresponds to a table or view in your database. So it makes perfect sense that you will get a combination of both!

How does DbContext work in Entity Framework?

As per Microsoft “A DbContext instance represents a session with the database and can be used to query and save instances of your entities. DbContext is a combination of the Unit Of Work and Repository patterns.” In simplified way we can say that DbContext is the bridge between Entity Framework and Database.


3 Answers

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
               };
    }
}
like image 177
Ivan Stoev Avatar answered Sep 23 '22 07:09

Ivan Stoev


For EF Core 5, you need to use the overload that accepts a StoreObjectIdentifier: GetColumnName(IProperty, StoreObjectIdentifier).

Update for EF 5:

var schema = entityType.GetSchema();
var tableName = entityType.GetTableName();
var storeObjectIdentifier = StoreObjectIdentifier.Table(tableName, schema);
var columnName = entityType.FindProperty(propertyName).GetColumnName(storeObjectIdentifier);
like image 40
Yasser Bazrforoosh Avatar answered Sep 25 '22 07:09

Yasser Bazrforoosh


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)")

like image 2
Ondrej Valenta Avatar answered Sep 23 '22 07:09

Ondrej Valenta