如何从Entity Framework Core的DbContext中获取列名和相应的数据库类型

25
假设我有这个表格:

enter image description here

如何在Entity Framework Core中从DbContext获取列名和数据库数据类型?

提示:

  1. EF Core Scaffold工具将名称为clg#的列转换为clg1,因此我需要真实的列名而不是当前的EF名称。

  2. 我需要数据库类型,而不是clrType,当然必须跨平台。也许我会更改数据库,所以该方法也必须可行。

期望结果:

    <D.clg#, int>
    <D.clgname, nvarchar(50)>
    <D.city, nvarchar(50)>
    <D.pname, nvarchar(50)>

有人能提供解决方案吗?

6个回答

59

更新(EF Core 3.x):从 EF Core 3.0 开始,元数据 API 再次发生了变化 - Relational() 扩展已被移除,属性被替换为 GetSet 扩展方法,所以现在代码看起来像这样:

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

更新(EF Core 2.x):从EF Core 2.0开始,情况发生了变化,因此原来的答案不再适用。现在EF Core为每种数据库类型构建单独的模型,因此代码更简单,直接使用Relational()扩展程序。

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

翻译后的答案 (EF Core 1.x):

与 EF 相比,获取关联元数据在 EF Core 中更加容易 - 从 DbContext.Model 属性开始获取 IModel,使用 GetEntityTypesFindEntityType 获取 IEntityType,然后使用 GetPropertiesFindProperty 获取 IProperty 等。

然而,问题在于 EF Core 允许您针对不同的目标数据库使用不同的设置。为了获取与上下文使用的当前数据库相对应的属性,您需要访问 IRelationalDatabaseProviderServices 并使用 AnnotationProviderTypeMapper 属性获取所需的信息。

这里是一个例子:

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

  1. tableSchema为null,但我认为它应该是SQLServer的[dbo]或Postgres的[public],那么我如何获取默认模式而不是NULL? 2)除了表名和模式之外,如何获取数据库名称,例如AdventureWorks或Northwind... 3)是否有一种使用元数据收集数据库品牌名称的方法?例如MSSqlServer、MySql、Firebird等,当然要跨平台!
- HamedFathi
2
不幸的是,所有这些都需要搜索EF Core源代码,因为它没有文档记录。(1)我现在完全不知道,我希望能够通过上面的方法得到它,但正如你所说,显然需要其他东西。(2)最终是dbContext.Database.GetDbConnection().Database。(3)不知道,最终从relationalDbServices.InvariantName中推导出来。 - Ivan Stoev
2
这个方法不再适用于EF Core 2,但是https://dev59.com/yJPfa4cB1Zd3GeqPFZus#45672102可以。 - smg
@smg 正确,已更新答案以反映此事,谢谢。 - Ivan Stoev
如何通过导航属性名称获取列名? - sp7
在 EF Core 5.0 中,getcolumnname() 被标记为过时 -> https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/breaking-changes#getcolumnname-obsolete - Doc

13

对于 EF Core 5,您需要使用接受 StoreObjectIdentifier 的重载:GetColumnName(IProperty, StoreObjectIdentifier)。

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

1
如果您从 context.Model.GetRelationalModel() 开始,则无需处理多个类型保存到同一表的情况,例如拥有的类型或类型层次结构。 - Jeremy Lakeman
1
entityType是什么?请在回答中添加您如何获得它的信息。 - Gert Arnold
foreach (var entityType in context.Model.GetEntityTypes()): - Yasser Bazrforoosh
var entityType = dbContext.Model.FindEntityType(clrEntityType); 变量entityType等于dbContext.Model.FindEntityType(clrEntityType); - Yasser Bazrforoosh

2
基于EFC3.1的答案,我创建了这个帮助程序,将所有表名和列名存储到一个单例字典中,在第一次使用时填充,以便代码不必一遍又一遍地遍历。我们用它来进行NPGSQL批量复制操作,似乎工作正常。此版本不会过滤实体类中的任何属性,因此在进行列名列表/字符串时要注意字段的顺序。但是据我所知,您只会得到在上下文中映射的属性,所以一切可能都没问题。 这个帮助程序
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();
    }
  }

创业公司注册

services.AddSingleton<ContextHelper>();

使用方法,类似于以下步骤

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


1
我创建并使用这个函数在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;
    }
}

您还可以将string Type替换为SqlDbType Type,因此foreach行将如下所示:

columns.Add(new ColumnInfo(
    property.GetColumnName(StoreObjectIdentifier.SqlQuery(entityType)), 
    Enum.Parse<SqlDbType>( property.GetColumnType().Slice(0,"(",true), true)));

在我的函数 Slice 中,有一段必要的代码:

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

1

对于那些来到这里但没有使用.NET CORE的人,就像我一样。 可以尝试以下方法:

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

也许有些冗余,但可以节省时间。

antonio


0
以下是我在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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接