哦,很棒的话题:)
使用可用的 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
,我们可以通过执行以下操作来获取列类型:a
b
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);
}
}
}
我希望它有所帮助,我知道这不仅仅是这样做,但希望能解释一些挑战:)
埃德