3

我正在编写一个针对 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 模型本身的限制。

4

1 回答 1

3

哦,很棒的话题:)

使用可用的 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]
)

对于列Id,,,,当我们使用强类型 dacfx 时StringCol,我们可以通过执行以下操作来获取列类型:ab

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 将它们解析为部分并尝试推断每个部分,我已经模拟了一个示例,该示例在这个视图可以让您了解您需要做什么,但这并不简单,我什至不想考虑可以传入动态 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);
        }
    }
}

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

埃德

于 2016-11-22T17:13:44.740 回答