com – 针对Intersystems Cache运行ObjectScript程序以向SQL Server提供数据

背景

我工作的主要应用程序主要基于InterSystems的MUMPS-esqueCaché数据库引擎.一切都存储在全局数组中.从外部报告系统中获取数据的范围从简单的痛苦到极度缓慢和痛苦.

Caché为数据库提供了一个ODBC驱动程序,但除非所涉及的全局数组恰好被选择标准键入,否则它将转向扫描,简单查询将需要数小时才能运行.为了扩展,整个Caché生产命名空间大约为100GB.在这些情况下,我可以编写ObjectScript(Intersystems的MUMPS方言)程序,它们比ODBC驱动程序更快地提取数据.

我认为部分问题是应用程序供应商不使用Caché的对象持久性支持,而是将SQL表定义为全局数组的外观,并且它通常不适用于批处理请求.

我在MS SQL Server中构建了一个报告数据库,它可以提取最常见的数据(价值2.5GB),即使必须扫描每个表,也会在3秒内返回所有结果.不幸的是,刷新数据需要很长时间,因此我只能每周进行一次完全刷新,每天进行一次主动刷新.这足以满足大多数需求,但我想做得更好.

我在Windows 7和Windows Server 2008 R2上使用Caché2007,SQL Server 2008 R2,VS2010.

问题范围

我需要一种方法将来自源Caché数据库的实时数据与SQL Server上的其他数据集成.我希望能够将视图或表值函数集成到SQL查询中,并从源数据库中提取实时数据.

>实时数据必须在SQL Server中可用于处理.使用辅助应用程序执行此操作将是一个巨大的痛苦,并且不适用于仅希望通过ODBC推送查询并以正确格式获取最终数据集的报表工具.
>我知道有一些方法可以将数据导入SQL Server或完成我想要做的相同的一般事情.这不是这个问题的关键所在.
>数据需要来自在Caché上运行的ObjectScript程序,因为我不需要通过SQL定义的表公开我需要的所有数据,并且我获得了使ObjectScript可以使用性能所需的控件.
>我正在寻找有关任何新选项的建议,或者我如何改进我尝试或考虑的选项之一或其他方法或缺点.

到目前为止我尝试过的

这个项目令人沮丧,我所研究的每一个有希望的大道要么是可怕的,要么是因为某种原因不起作用.通常原因是对SQLCLR组件有一些不必要的限制.

>通过链接服务器通过InterSystem的CachéODBC驱动程序提取所有内容.如果SQL Server无法将条件推送到远程服务器或必须在本地执行连接,则SQL Server通常会进行扫描.扫描任何非平凡的表需要花费数小时,这是不可接受的.此外,Caché中的SQL表定义错误地定义了许多列的长度. SQL Server不喜欢它并中止查询.请参阅this SO question.我无法更改表defs,供应商认为这不是问题,因为它适用于MS Access.
>按需使用OPENQUERY.这在某种程度上起作用,但我仍然可以从前一项中得到列长度问题,并且无法参数化OPENQUERY查询,因此无法提取上下文数据.
>使用SQLCLR通过CLR表值函数调用ODBC数据提供程序.这会解决参数化和数据长度问题,尽管每次我需要新的数据时都需要我定义或修改函数.不幸的是,并非所有我感兴趣的数据元素都可以通过SQL获得.对于某些事情,我需要直接访问全局数组.
> Intersystems提供了一个ActiveX control,允许您在服务器上通过TCP运行ObjectScript程序并获得结果.这在一个独立的C#应用​​程序中运行良好,但是当我尝试从SQLCLR程序集建立连接时,我得到一个荒谬的URI错误:

在执行用户定义的例程或聚合“GetActiveAccounts”期间发生.NET Framework错误:
System.UriFormatException:无效的URI:URI为空.
System.UriFormatException:
在System.Uri.CreateThis(String uri,Boolean dontEscape,UriKind uriKind)
在System.Uri..ctor(String uriString)
在System.ComponentModel.Design.RuntimeLicenseContext.GetLocalPath(String fileName)
at System.ComponentModel.Design.RuntimeLicenseContext.GetSavedLicenseKey(Type type,Assembly resourceAssembly)
在System.ComponentModel.LicenseManager.LicenseInteropHelper.GetCurrentContextInfo(Int32& fDesignTime,IntPtr& bstrKey,RuntimeTypeHandlerth)
在FacsAccess.GetActiveAccounts.Client.connect()
在FacsAccess.GetActiveAccounts.Client..ctor()
在FacsAccess.GetActiveAccounts.E1.GetEnumerator()

this unanswered SO question.网上还有其他postings,但似乎没有人知道.这是一个非常简单的COM包装器,而不是C DLL;它没有对许可做任何事情,也没有理由进入托管许可库.我想知道这是否是某种样板试图获取没有名称的程序集的名称,因为它已被加载到SQL数据库中.
> Intersystems还提供了更直接的unmanaged interface,但这些接口都是C,我无法通过P / Invoke使用,我无法在SQLCLR中加载C/C++LI混合模式不纯组件.

我考虑过的选项但看起来有点可怕

>我已经考虑过通过SQL Server的COM支持来尝试ActiveX控件,但这非常慢且非常麻烦.
>我可以创建一个进程外服务来代理流量,但是我不能使用来自SQLCLR的.NET远程处理,你不应该使用WCF,无论如何它对于这么简单的接口来说真的很重要.我很快就会推出自己的IPC界面.
>我可以为VisM或CacheDirect接口编写一些带有C风格接口的额外非托管包装器,并通过P / Invoke访问THAT.

看起来这应该不是那么难,但它确实让我起了作用,我需要一些观点.

最佳答案 我认为您可以通过链接服务器使用ODBC访问缓存数据库上的存储过程,这些存储过程对ODBC驱动程序可见并且返回结果集,但不是使用SQL实现的.

我100%肯定你可以创建这样的存储过程并通过ODBC访问它们,但我从未尝试从SQL服务器访问它们作为链接服务器.即使链接服务器不起作用,似乎最好通过Intersystem的ODBC驱动程序而不是Active X控件或CacheDirect进行访问.

我有一个 this question这样的程序的例子.

如果链接死亡,这里是代码:

Query OneGlobal(GlobalName As %String) As %Query(ROWSPEC = "NodeValue:%String,Sub1:%String,Sub2:%String,Sub3:%String,Sub4:%String,Sub5:%String,Sub6:%String,Sub7:%String,Sub8:%String,Sub9:%String") [SqlProc]
{
}

ClassMethod OneGlobalExecute(ByRef qHandle As %Binary, GlobalName As %String) As %Status
{
    S qHandle="^"_GlobalName
    Quit $$$OK
}

ClassMethod OneGlobalClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = OneGlobalExecute ]
{
    Quit $$$OK
}

ClassMethod OneGlobalFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = OneGlobalExecute ]
{

    S Q=qHandle  
    S Q=$Q(@Q)  b  
    I Q="" S Row="",AtEnd=1 Q $$$OK
    S Depth=$QL(Q)
    S $LI(Row,1)=$G(@Q)
    F I=1:1:Depth S $LI(Row,I+1)=$QS(Q,I)
    F I=Depth+1:1:9 S $LI(Row,I+1)=""
    S AtEnd=0
    S qHandle=Q
    Quit $$$OK
}

仅供参考,这不是在生产中使用的示例,因为它公开了来自所有全局变量的所有数据.

但是,听起来你似乎准备编写缓存对象脚本来直接获取数据 – 这是一个模板.要理解的主要是qHandle将在每次调用时由ODBC驱动程序传回,因此您可以使用它来存储状态.如果存在很多状态,则将qHandle作为整数索引转换为保持“实际”状态的临时全局,并在close方法中清除该状态.

由于您关注性能,您可能还想实现一个

MyQueryFetchRows (ByRef qHandle As %Binary, FetchCount As %Integer = 0, ByRef RowSet As %List, ByRef ReturnCount As %Integer, ByRef AtEnd As %Integer) As %Status

方法 – 有关详细信息,请参阅%Library.Query的文档.

如果你真的需要这个作为(只读)表而不是存储过程出现在ODBC中,我认为它可能是 – 但我以前从未试图查看任意存储过程是否可以作为读取公开只有桌子,我不确定它是多么容易,或者它是否真的总是可能的.

点赞