【问题标题】:Determining Primary Key columns via GetSchema通过 GetSchema 确定主键列
【发布时间】:2013-02-18 20:45:37
【问题描述】:

有没有办法使用 ADO.NET GetSchema 方法来确定列是否为主键?

这是我目前得到的:

public IEnumerable<DbColumnInfo> GetColumns(string providerName, string connectionString, string tableName)
{
    DbProviderFactory factory = DbProviderFactories.GetFactory(providerName);
    using (DbConnection connection = factory.CreateConnection())
    using (DbCommand command = factory.CreateCommand())
    {
        connection.ConnectionString = connectionString;
        connection.Open();
        command.Connection = connection;

        var columns = connection.GetSchema("Columns", tableName.Split('.'));
        foreach (DataRow row in columns.Rows)
        {
            yield return new DbColumnInfo()
            {
                Name = row.Field<string>(3),
                OrdinalPosition = row.Field<short>(4),
                DataType = this.FormatDataType(row),
                IsNullable = string.Equals(row.Field<string>(6), "yes", StringComparison.InvariantCultureIgnoreCase),
                IsPrimaryKey = // ... ?
            };
        }
    }
}

【问题讨论】:

  • 您必须使用 GetSchema 还是愿意使用常规查询?当您展开表的列节点并从中调整查询时,我使用 Sql Profiler 来跟踪 Sql Management Studio 所做的事情。明天上班的时候,如果对你有用的话,我会发上来的。
  • @pswg 我知道这已经一岁了,但我很好奇你是否有一个体面的FormatDataType 实现,你愿意分享一个要点或其他东西,或者就此而言查看和/或您最终提出的其他提供程序代码?
  • @qes 最后我决定使用 SQL 查询而不是 ADO 来获取我需要的信息。请参阅下面的答案。

标签: c# .net tsql ado.net


【解决方案1】:

用connection.GetSchema()恐怕你无法确定...

但作为一种解决方法,您可以尝试使用 dataadapter,如果它适合您:

    var da = factory.CreateDataAdapter();
    command.CommandText = "select * from Employees";
    da.SelectCommand = command;
    da.MissingSchemaAction = MissingSchemaAction.AddWithKey;

    var dtab = new DataTable();
    da.FillSchema(dtab, SchemaType.Source);

    foreach (DataColumn col in dtab.Columns)
    {
        string name = col.ColumnName;
        bool isNull = col.AllowDBNull;
        bool isPrimary = dtab.PrimaryKey.Contains(col);
    }

【讨论】:

  • 我已经考虑过了,但我还需要可靠地检索列的底层 SQL 数据类型(例如 NVARCHAR(255)System.String 相对)。 AFAIK GetSchema("Columns") 是唯一的方法。我真的希望避免对数据库进行 2 次调用。
  • 我不知道,谢谢。也许从系统视图中选择?但这将特定于给定的提供者......
  • 我最终选择了一个解决方案,它为 SQL 服务器提供程序使用系统视图,并为更通用的提供程序使用 FillSchemaGetSchema 的组合。它并不漂亮,但它在我需要它的提供者上运行得相当好。感谢您的帮助。
【解决方案2】:

自从 qes 问起,我目前正在使用的东西(自从 Laszlo 提供了他的工作解决方案以来,经过几个版本重构代码之后)并不像我希望的那样优雅为,但它更有效并满足了我的需求。基本上我提供了一个DbInfoProvider 类,该类旨在为给定的提供者生成DbTableInfoDbColumnInfoSqlDbInfoProvider 仅使用它来获取 SQL Server 的列信息:

public IEnumerable<DbColumnInfo> GetColumns(string connectionString, DbTableInfo table)
{
    DbProviderFactory factory = DbProviderFactories.GetFactory(this.providerName);
    using (DbConnection connection = factory.CreateConnection())
    using (DbCommand command = factory.CreateCommand())
    {
        connection.ConnectionString = connectionString;
        connection.Open();
        command.Connection = connection;
        command.CommandText = ColumnInfoQuery;
        command.CommandType = CommandType.Text;
        var tableSchema = factory.CreateParameter();
        tableSchema.ParameterName = "@tableSchema";
        tableSchema.DbType = DbType.String;
        tableSchema.Value = table.Schema;
        command.Parameters.Add(tableSchema);
        var tableName = factory.CreateParameter();
        tableName.ParameterName = "@tableName";
        tableName.DbType = DbType.String;
        tableName.Value = table.Name;
        command.Parameters.Add(tableName);

        var dataTable = new DataTable();
        using (var reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                yield return new DbColumnInfo()
                {
                    Name = reader.GetString(0),
                    OrdinalPosition = reader.GetInt32(1),
                    DataType = reader.GetString(2),
                    IsNullable = reader.GetBoolean(3),
                    IsPrimaryKey = reader.GetBoolean(4),
                    IsForeignKey = reader.GetBoolean(5),
                    IsUnique = reader.GetBoolean(6),
                };
            }
        }
    }
}

其中ColumnInfoQuery 是这样的静态字符串:

SELECT c.[column_name]
     , CAST(c.[ordinal_position] AS int) [ordinal_position]
     , CASE WHEN c.[data_type] IN ( 'bit', 'date', 'datetime', 'smalldatetime', 'int', 'bigint', 'smallint', 'tinyint', 'real', 'money', 'smallmoney', 'image', 'text', 'ntext' )  THEN c.[data_type]
            WHEN c.[character_maximum_length] IS NOT NULL
            THEN c.[data_type] + '(' + CAST(c.[character_maximum_length] AS VARCHAR(30)) + ')'
            WHEN c.[datetime_precision] IS NOT NULL 
            THEN c.[data_type] + '(' + CAST(c.[datetime_precision] AS VARCHAR(30)) + ')'
            WHEN c.[numeric_scale] IS NOT NULL 
            THEN c.[data_type] + '(' + CAST(c.[numeric_precision] AS VARCHAR(30)) + ',' + CAST(c.[numeric_scale] AS VARCHAR(30)) + ')'
            WHEN c.[numeric_precision] IS NOT NULL 
            THEN c.[data_type] + '(' + CAST(c.[numeric_precision] AS VARCHAR(30)) + ')'
            ELSE c.[data_type]
       END [data_type]
     , CAST(MAX(CASE c.[is_nullable] WHEN 'YES' THEN 1 ELSE 0 END) AS bit) [is_nullable]
     , CAST(MAX(CASE WHEN pk.[constraint_type] = 'PRIMARY KEY' THEN 1 ELSE 0 END) AS bit) [is_primary_key]
     , CAST(MAX(CASE WHEN pk.[constraint_type] = 'FOREIGN KEY' THEN 1 ELSE 0 END) AS bit) [is_foreign_key]
     , CAST(MAX(CASE WHEN pk.[constraint_type] = 'FOREIGN KEY' THEN 0 ELSE 1 END) AS bit) [is_unique]
FROM information_schema.columns c
LEFT JOIN information_schema.constraint_column_usage ccu 
        ON c.[column_name] = ccu.[column_name] 
        AND c.[table_name] = ccu.[table_name] 
        AND c.[table_schema] = ccu.[table_schema]
        AND c.[table_catalog] = ccu.[table_catalog]
LEFT JOIN information_schema.table_constraints pk 
        ON pk.[constraint_name] = ccu.[constraint_name]
        AND pk.[table_name] = ccu.[table_name] 
        AND pk.[constraint_schema] = ccu.[table_schema]
        AND pk.[constraint_catalog] = ccu.[table_catalog]
        AND pk.[constraint_type] IN ( 'PRIMARY KEY', 'FOREIGN KEY', 'UNIQUE' )
WHERE c.[table_schema] = @tableSchema
        AND c.[table_name] = @tableName
GROUP BY c.[table_schema], c.[table_name], c.[column_name], c.[ordinal_position]
       , c.[data_type], c.[character_maximum_length], c.[datetime_precision]
       , c.[numeric_precision], c.[numeric_scale], c.[is_nullable]

【讨论】:

【解决方案3】:

是的, 您可以通过请求索引的架构来确定哪些列是主键。然后搜索特定列/表的索引

DataTable indexes = conn.GetSchema("Indexes");
List<string> PrimaryKeys = new List<string>();
foreach (DataRow row in indexes.Rows)
  if (Convert.ToBoolean(row["PRIMARY_KEY"]))
    PrimaryKeys.Add(row["TABLE_NAME"] + "." + row["COLUMN_NAME"]);

PrimaryKeys 将包含数据库中的主键列表。只需检查您的 [table].[column] 是否在此列表中。

【讨论】:

  • 我认为这并不适用于 Sql Server 的每个服务器版本,vr 2008 不会返回“PRIMARY_KEY”列
  • 我也无法让它在 SQL Server 2016 上运行...:/
【解决方案4】:

这适用于 SQLITE 和可能的其他数据库:-

            string[] restrictions = new string[] { null, null,   strTable };
            DataTable tableInfo = Connection.GetSchema("IndexColumns", restrictions);

            if (tableInfo == null)
                throw new Exception("TableInfo null Error");

            foreach (DataRow test in tableInfo.Rows)
            {
                Console.WriteLine(test["column_name"]);             
            }

【讨论】:

【解决方案5】:

至少对于 Oracle ODP.Net 提供程序,GetSchema() 有一个“PrimaryKeys”选项。我建议您使用不带参数的 GetSchema() 来查看为您的实现定义了哪些其他调用。此代码检索 GetSchema() 元定义,该定义描述了 Oracle 提供程序的其他已实现 GetSchema() 调用。

    private void GetSchemaMetaInfo(DbConnection connection)
    {
        var metaDataCollections = connection.GetSchema("MetaDataCollections");
        var dataSourceInformation = connection.GetSchema("DataSourceInformation");
        var dataTypes = connection.GetSchema("DataTypes");
        var restrictions = connection.GetSchema("Restrictions");
        var reservedWords = connection.GetSchema("ReservedWords");
    }

【讨论】:

    【解决方案6】:

    可以使用PrimaryKeys集合来确定主键 对于您的特定表,然后检索相应的索引名称,然后从 IndexColumns 集合中获取构成索引和主键的列。

    String pkIndxNm = null;
    List<String> lstPkColNms = new List<String>();
    
    DataTable dt = dbConn.GetSchema("PrimaryKeys", new[] { owner, tblNm });
    if (dt.Rows.Count == 1) {
        DataRow pkRow = dt.Rows[0];
        pkIndxNm = pkRow["INDEX_NAME"] as String;
    
        // Now use the IndexColumns collection to pick up the names of the 
        // columns which constitute the primary key.
        //
        dt = dbConn.GetSchema("IndexColumns", new[] { owner, pkIndxNm });
        foreach (DataRow icRow in dt.Rows) {
            String colNm = icRow["COLUMN_NAME"] as String;
            lstPkColNms.Add(colNm);
        }
    }
    

    这适用于使用 System.Data.Common.DbConnection 的 Oracle 数据库 具有底层 System.Data.OracleClient 连接的类。

    对于其他数据库,您可能需要检查 Restrictions 集合 你可以作为参数传递什么。 GetSchema 限制的文档 似乎很没用。我发现只是转储的 DataTable 限制集合是了解每个集合如何 最好查询:

      DataTable dt = dbConn.GetSchema("Restrictions");
      AppLog.Log.Info("CollectionName | RestrictionName | ParameterName | " +
                      "RestrictionDefault | RestrictionNumber");
      AppLog.Log.Info(" ");
      foreach (DataRow r in dt.Rows) {
          String s = r["CollectionName"] as String;
          s += " | " + r["RestrictionName"] as String;
          s += " | " + r["ParameterName"] as String;
          s += " | " + r["RestrictionDefault"] as String;
          s += " | " + r["RestrictionNumber"].ToString();
          AppLog.Log.Info(s);
      }
    

    这是我得到的,从中我知道在 数组的第 1 和第 2 位置以将其约束到正确的索引。

    用户 |用户名 |姓名 |用户名 | 1

    表 |所有者 |所有者 |所有者 | 1

    表 |表 |表名 |表名 | 2

    列 |所有者 |所有者 |所有者 | 1

    列 |表 |表名 |表名 | 2

    列 |专栏 |栏名 | COLUMN_NAME | 3

    观看次数 |所有者 |所有者 |所有者 | 1

    观看次数 |查看 |视图名 | VIEW_NAME | 2

    同义词 |所有者 |所有者 |所有者 | 1

    同义词 |同义词 |同义词 | SYNONYM_NAME | 2

    序列 |所有者 |所有者 | SEQUENCE_OWNER | 1

    序列 |序列 |序列 | SEQUENCE_NAME | 2

    过程参数 |所有者 |所有者 |所有者 | 1

    过程参数 |对象名 |对象名 |对象名 | 2

    功能 |所有者 |所有者 |所有者 | 1

    功能 |姓名 |姓名 |对象名 | 2

    索引列 |所有者 |所有者 |索引所有者 | 1

    索引列 |姓名 |姓名 |索引名称 | 2

    索引列 |桌主 |桌主 |表所有者 | 3

    索引列 |表名 |表名 |表名 | 4

    索引列 |专栏 |栏名 | COLUMN_NAME | 5

    索引 |所有者 |所有者 |所有者 | 1

    索引 |姓名 |姓名 |索引名称 | 2

    索引 |桌主 |桌主 |表所有者 | 3

    索引 |表名 |表名 |表名 | 4

    包裹 |所有者 |所有者 |所有者 | 1

    包裹 |姓名 |包名 |对象名 | 2

    PackageBody |所有者 |所有者 |所有者 | 1

    PackageBody |姓名 |姓名 |对象名 | 2

    参数 |所有者 |所有者 |所有者 | 1

    参数 |包名 |包名 |包名 | 2

    参数 |对象名 |对象名 |对象名 | 3

    参数 |参数名称 |参数名称 | ARGUMENT_NAME | 4

    程序 |所有者 |所有者 |所有者 | 1

    程序 |姓名 |姓名 |对象名 | 2

    唯一键 |所有者 |所有者 |所有者 | 1

    唯一键 |表名 |表名 |表名 | 2

    唯一键 |约束名称 |约束名称 |约束_名称 | 3

    主键 |所有者 |所有者 |所有者 | 1

    主键 |表名 |表名 |表名 | 2

    主键 |约束名称 |约束名称 |约束_名称 | 3

    外键 | Foreign_Key_Owner |所有者 | FKCON.OWNER | 1

    外键 | Foreign_Key_Table_Name |表名 | FKCON.TABLE_NAME | 2

    外键 | Foreign_Key_Constraint_Name |约束名称 | FKCON.CONSTRAINT_NAME | 3

    外键列 |所有者 |所有者 | FKCOLS.OWNER | 1

    外键列 |表名 |表名 | FKCOLS.TABLE_NAME | 2

    外键列 |约束名称 |约束名称 | FKCOLS.CONSTRAINT_NAME | 3

    【讨论】:

    • 对于 MSSQL 2016,“PrimaryKeys”集合(如您在答案中所示)根本不存在。所以我认为这不适用于 MSSQL 服务器。
    猜你喜欢
    • 2012-06-25
    • 1970-01-01
    • 2018-06-29
    • 1970-01-01
    • 1970-01-01
    • 2017-11-03
    • 2018-01-05
    • 1970-01-01
    • 2020-10-19
    相关资源
    最近更新 更多