【问题标题】:Finding data type information for certain objects in DacFx API在 DacFx API 中查找某些对象的数据类型信息
【发布时间】:2016-11-22 16:34:50
【问题描述】:

我正在编写一个针对 Sql Server Data Tools 的代码生成工具,我需要能够获取以下数据类型:

  1. 查看列
  2. 表中的计算列

这些信息在哪里?对于表(计算列除外),它在这里:

TSqlObject table;  //retrieved elsewhere
TSqlObject column = table.GetReferenced(Table.Columns).First(); //first column
TSqlObject dataType = column.GetReferenced(Column.DataType).FirstOrDefault();

对于计算列,上面的dataType 为空。

对于视图,我已经尝试过:

TSqlObject view;  //retrieved elsewhere
TSqlObject column = view.GetReferenced(View.Columns).First(); //first column
TSqlObject dataType = column.GetReferenced(Column.DataType).FirstOrDefault();//null

这些信息是否在任何地方?除了将源 .DACPAC 发布到数据库之外,还有其他方法可以获取此信息吗?


编辑:回应下面的 Ed Elliot(关于使用强类型 DacFx 模型)

以下代码无法返回视图的类型信息:

TSqlTypedModel model = new TSqlTypedModel(@"path.dacpac");
var view = model.GetObjects<TSqlView>(Microsoft.SqlServer.Dac.Model.DacQueryScopes.UserDefined).FirstOrDefault();
var viewcolumns = view.Columns;

//false
bool viewHasTypeInformation = viewcolumns.Any(c => c.DataType.Count() > 0); 

var table = model.GetObjects<TSqlTable>(Microsoft.SqlServer.Dac.Model.DacQueryScopes.UserDefined).FirstOrDefault();
var tablecolumns = table.Columns;

//true
bool tableHasTypeInformation = tablecolumns.Any(c => c.DataType.Count() > 0);

我开始认为这是 DAC 模型本身的限制。

【问题讨论】:

    标签: sql-server sql-server-data-tools dac


    【解决方案1】:

    哦,很棒的话题:)

    使用可用的 DacFxStronglyTypedModel 对象进行查询的最简单方法:

    https://github.com/Microsoft/DACExtensions

    有点奇怪,因为它是您构建的示例,然后您可以轻松访问查询 DacFx:

    https://github.com/Microsoft/DACExtensions/tree/master/DacFxStronglyTypedModel

    要获得强类型模型,请执行以下操作:

    var model = new TSqlTypedModel("dacpacPath");
    

    然后,当您查询所有视图(或其他)时,您会得到一个比 DacFx 更“理智”的类型对象列表。

    返回查看的界面:

    ISql120TSqlView(把版本号改成你的版本号)有一个 IEnumerable 的列:

    IEnumerable<Microsoft.SqlServer.Dac.Extensions.Prototype.ISql120TSqlColumn> Columns 
    {
        get;
    }
    

    然后列接口有一个数据类型的 IEnumerable:

    IEnumerable<Microsoft.SqlServer.Dac.Extensions.Prototype.ISqlDataType> DataType 
    {
        get;
    }
    

    我现在没有一台 Windows 机器来给你一个完整的演示,但这应该足够了,如果你没有得到你需要的东西,请发表评论,我明天会得到一个样本(如果没有其他人这样做同时)。

    要获取视图上的列列表,请执行以下操作:

    var views = model.GetObjects<TSqlView>(DacQueryScopes.UserDefined);
    foreach (var v in views)
    {
        Console.WriteLine(v.Columns.Count());
    }
    

    这适用于我的 130 版本的 Dac dll。

    要获取计算列的详细信息,您需要查看列上的“ExpressionDependencies”(参见v.Columns),这与表相同。


    编辑

    所以我一直在玩,有些事情直到运行时才能确定,所以 DacFx 将无法确定这些类型,它们只有我知道它实际生成记录集并检查您返回的内容,但如果我们举这个例子,我们可以对计算列做一些事情:

    create table [dbo].[the_table]
    (
        [Id] INT not null primary key,
        [StringCol] varchar(234) not null,
        [a] int,
        [b] decimal,
        [a_and_b] as [a] + [b]
    )
    

    对于列,IdStringColab,当我们使用强类型 dacfx 时,我们可以通过这样做来获取列类型:

    var tables = model.GetObjects(DacQueryScopes.UserDefined); 
    foreach (var t in tables) 
    { 
        switch (c.ColumnType) 
        { 
            case ColumnType.Column: 
                ShowType(c.DataType); 
                break;
        }
    }
    

    ShowType 看起来像这样:

    void ShowType(IEnumerable<ISqlDataType> types)
    {
        var builder = new StringBuilder();
    
        foreach (var type in types)
        {
            var t = new TSqlDataType(type.Element);
            builder.Append($"{t.SqlDataType.ToString()} ");
        }
    
        Console.Write(builder);
    }
    

    我们所做的是为每一列创建一个数据类型列表,它可能只是 int 或类似的东西,但它是一个列表。

    现在因为我们有一个计算列,而不仅仅是数据类型,我们有对基础列的引用,我们可以从中获取数据类型:

    void ShowDependencies(IEnumerable<ISqlModelElementReference> dependencies)
    {
        foreach (var dependency in dependencies)
        {
            if (dependency is TSqlColumnReference)
            {
                var column = new TSqlColumn(dependency.Element);
                Console.Write(column.Name + " ");
                ShowType(column.DataType);
            }
        }
    }
    

    知道何时调用此版本:

    var tables = model.GetObjects<TSqlTable>(DacQueryScopes.UserDefined);
    
    foreach (var t in tables)
    {
        Console.WriteLine($"table - {t.Name}");
        foreach (var c in t.Columns)
        {
            Console.Write("\r\n" + c.Name.ToString() + " ");
            switch (c.ColumnType)
            {
                case ColumnType.Column:
                    ShowType(c.DataType);
                    break;
                case ColumnType.ComputedColumn:
                    Console.Write($"({c.Expression}) ");
                    ShowDependencies(c.ExpressionDependencies);
                    break;
    

    对于我们得到这个输出的样本表:

    table - [dbo].[the_table]
    
    [dbo].[the_table].[Id] Int
    [dbo].[the_table].[StringCol] VarChar
    [dbo].[the_table].[a] Int
    [dbo].[the_table].[b] Decimal
    [dbo].[the_table].[a_and_b] ([a] + [b]) [dbo].[the_table].[a] Int [dbo].[the_table].[b] Decimal view - [dbo].[mutli_type]
    

    然后我们需要确定类型是什么,因为猜测 sql 会在运行时执行隐式转换为小数,但在编译时我认为它不知道(很高兴在这里得到纠正!)

    如果我们再以视图为例:

    create view the_view
    as
    select
        *,
        object_name(4) some_name,
        123 as an_int
    from
        the_table
    

    我们有来自基表的列,可以简单地枚举,但 object_name 和 123 稍微难一些,使用与上面相同的代码,但对于我们得到的视图:

    [dbo].[the_view].[Id] [dbo].[the_table].[Id] Int
    [dbo].[the_view].[StringCol] [dbo].[the_table].[StringCol] VarChar
    [dbo].[the_view].[a] [dbo].[the_table].[a] Int
    [dbo].[the_view].[b] [dbo].[the_table].[b] Decimal
    [dbo].[the_view].[a_and_b] [dbo].[the_table].[a_and_b]
    [dbo].[the_view].[some_name] some_name = an_int =
    [dbo].[the_view].[an_int] some_name = an_int =
    

    所以计算列没有类型加上要获得 a_and_b 的值,我们需要再次进一步枚举以获得我们上面的类型。

    此时您所拥有的是一个包含指向函数/表达式的列的视图,这就是开始变得更加困难的地方:) 如果您采用上面的示例,您可能会计算出 object_name 返回的内容并确定它,但是当您会得到一个对数据或数据类型都不确定的视图,您会怎么做?

    如果我们采取:

    create view mutli_type
    as
        select case datepart(day, getdate()) 
            when 1
                then 100
            when 2 
                then 'hello'
            else
                getdate()
            end as multitype
    

    取决于我们返回不同数据类型的日期 - 哎呀。

    如果您真的需要知道视图返回什么,您可以获取视图中的选择元素并使用 TSqlScript Dom 将它们解析为部分并尝试推断每个部分,我已经模拟了一个找到 getdate( ) 函数让您了解需要做什么,但这并不简单,我什至不想考虑可以传入动态 sql 的存储过程:


    完整示例:

    create table [dbo].[the_table]
    (
        [Id] INT not null primary key,
        [StringCol] varchar(234) not null,
        [a] int,
        [b] decimal,
        [a_and_b] as [a] + [b]
    )
    go
    create view the_view
    as
    select *, object_name(4) some_name, 123 as an_int from the_table
    go
    create view mutli_type
    as
        select case datepart(day, getdate()) 
            when 1
                then 100
            when 2 
                then 'hello'
            else
                getdate()
            end as multitype
    go
    
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.SqlServer.Dac.Extensions.Prototype;
    using Microsoft.SqlServer.Dac.Model;
    using Microsoft.SqlServer.TransactSql.ScriptDom;
    using ColumnType = Microsoft.SqlServer.Dac.Model.ColumnType;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void ShowType(IEnumerable<ISqlDataType> types)
            {
                var builder = new StringBuilder();
    
                foreach (var type in types)
                {
                    var t = new TSqlDataType(type.Element);
                    builder.Append($"{t.SqlDataType.ToString()} ");
                }
    
                Console.Write(builder);
            }
    
            static void ShowDependencies(IEnumerable<ISqlModelElementReference> dependencies)
            {
                foreach (var dependency in dependencies)
                {
                    if (dependency is TSqlColumnReference)
                    {
                        var column = new TSqlColumn(dependency.Element);
                        Console.Write(column.Name + " ");
                        ShowType(column.DataType);
                    }
                }
            }
            
            static void Main(string[] args)
            {
    
                var model = new TSqlTypedModel(@"path\Da.dacpac");
    
                var views = model.GetObjects<TSqlView>(DacQueryScopes.UserDefined);
    
                var tables = model.GetObjects<TSqlTable>(DacQueryScopes.UserDefined);
    
                foreach (var t in tables)
                {
                    Console.WriteLine($"table - {t.Name}");
                    foreach (var c in t.Columns)
                    {
                        Console.Write("\r\n" + c.Name.ToString() + " ");
                        switch (c.ColumnType)
                        {
                            case ColumnType.Column:
                                ShowType(c.DataType);
                                break;
                            case ColumnType.ComputedColumn:
                                Console.Write($"({c.Expression}) ");
                                ShowDependencies(c.ExpressionDependencies);
                                break;
                            case ColumnType.ColumnSet:
                                break;
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                    }
                }
    
    
                foreach (var v in views)
                {
                    Console.WriteLine($"view - {v.Name}");
    
                    foreach (var c in v.Columns)
                    {
                        Console.Write("\r\n" + c.Name.ToString() + " ");
    
                        var needDomParse = false;
    
                        switch (c.ColumnType)
                        {
                            case ColumnType.Column:
    
                                ShowType(c.DataType);
                                ShowDependencies(c.ExpressionDependencies);
                                
                                break;
                            case ColumnType.ComputedColumn:
    
                                ShowType(c.DataType);
                                ShowDependencies(c.ExpressionDependencies);
    
                                if (!c.DataType.Any() && !c.ExpressionDependencies.Any())
                                {
                                    needDomParse = true;
                                }
    
                                break;
                            case ColumnType.ColumnSet:
                                break;
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
    
                        if (needDomParse)
                        {
                            //ouch
                            
                            var create = new CreateViewStatement();
                            var parser = new TSql130Parser(false);
                            IList<ParseError> errors;
                            var fragment = parser.Parse(new StringReader(v.GetScript()), out errors);
                            var selectVisitor = new SelectVisitor();
                            fragment.Accept(selectVisitor);
    
                            foreach (var s in selectVisitor.Selects)
                            {
                                var spec = s.QueryExpression as QuerySpecification;
                                foreach (var element in spec.SelectElements)
                                {
                                    var select = element as SelectScalarExpression;
                                    if (select != null)
                                    {
                                        Console.Write(select.ColumnName.Value + " = ");
                                        var caseExpression = select.Expression as SimpleCaseExpression;
                                        if (caseExpression != null)
                                        {
                                            var func = caseExpression.ElseExpression as FunctionCall;
                                            Console.WriteLine(func.FunctionName.Value);
                                        }
    
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    
        internal class SelectVisitor : TSqlConcreteFragmentVisitor
        {
            public List<SelectStatement> Selects = new List<SelectStatement>();
            public override void Visit(SelectStatement node)
            {
                Selects.Add(node);
                base.Visit(node);
            }
        }
    }
    
    

    我希望它有所帮助,我知道这不仅仅是这样做,但希望能解释一些挑战:)

    埃德

    【讨论】:

    • 我实际上是直接查看强类型模型的源代码... (除了他们使用 GetReferencedRelationshipInstances 而不是 GetReferenced,我也尝试过)。稍后我会在这里尝试一下,但我确信他们的代码也不会显示任何数据类型信息。
    • 我已经添加到答案中 - 如果你不能让它工作,你可以给一些你想要找出的示例 t-sql,我应该可以给你一个示例它
    • 我正在专门寻找视图列的数据类型。您的代码确实有效,但尝试获取 view.Columns[0].DataType - 没有任何返回
    • 视图列的定义是什么?查看 view.Columns[0].ExpressionDependencies - 它应该指向表上的列,然后您可以获取其类型。在 t-sql 中,您没有定义视图中列的类型,但列和第列具有类型,或者如果它类似于函数,那么我猜是函数的返回类型。
    • 我没有定义,我试图让这段代码通用。如果视图定义类似于:select cast(Datetimecolumn as date) as datePart 怎么办?视图列的类型不一定是基础列的类型。不过我明白你的意思……SQL Server 是唯一可以真正确定视图类型的技术吗(因为它没有真正声明,而是推断出来的)?
    猜你喜欢
    • 2017-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-23
    • 2013-12-13
    • 2016-04-14
    相关资源
    最近更新 更多