2013-03-12 89 views
2

我想创建一个.Net DLL基本上作为数据库连接的抽象层;它将取代我们拥有的用VB6编写的当前DLL,并且我试图尽可能地匹配当前的功能。通过COM接口将.NET DataTable属性公开给VBA

不管怎么说,我有必要的问题是,我不能找到一个办法让.NET类像DataColumnCollection或DataColumn中的VBA解释器显示 - 它可能会说,例如,“列”与键入“MarshalByValueComponent”,但值将为“无变量”。

如果我完全重新创建两个类(即Fields作为字段的集合,从DataColumn继承,然后为这两个接口定义接口),我可以使它工作,但是好像增加了很多开销为什么(应该是?)一个非常简单的想法。我觉得我只是想用编组器处理DataColumn类的方式丢失一些非常简单的东西。

我在网上找到的很多东西是关于如何将DataTable或DataReader转换为遗留的ADODB Recordset,但这也会增加很多开销......我宁愿将它作为DataTable放置,创建一个COM接口以允许VBA与之交互;那样的话,例如,如果他们想把表写入excel工作表,我不会复制工作(转换为ADODB记录集,然后读取/写入Excel工作表),您需要迭代整个表两次。 ..)

对不起,书的长度解释 - 我觉得这个问题需要一点澄清,因为根本原因是试图匹配遗留功能。这是我当前界面的例子不工作:

Public Interface IDataTable 
    ReadOnly Property Column As DataColumn 
End Interface 

<ClassInterface(ClassInterfaceType.None)> _ 
<System.ComponentModel.DesignerCategory("")> _ 
<ComDefaultInterface(GetType(Recordset.IDataTable))> _ 
<Guid("E7AFBBB6-CB20-44EC-9CD2-BC70B94CD8B7")> _ 
Public Class Recordset : Inherits Data.DataTable : Implements IDataTable 

Public ReadOnly Property Column As DataColumn Implements IDataTable.Column 
    Get 
     Return MyBase.Columns(0) 
    End Get 
End Property 

注:我最初试图属性列,其中DataColumnCollection返回MyBase.Columns。这是作为Object而不是MarshalByValueComponent传递的,但也是空的。我知道MyBase.Column(0)有一个值,因为我可以把Msgbox(MyBase.Columns(0).ColumnName)放在get的返回之上,它弹出正常(不要判断;这样比使用一个调试器)...

我不介意只是定义它们两个,但我不能继承DataColumnCollection和COM接口已经很烂处理泛型。有没有其他解决方法,而不需要重新发明轮子?

感谢您的帮助!

+0

您是否考虑过使用.NET DAO 3.6 Object Library? – 2013-03-12 15:31:39

+0

您的意思是使用DAO Recordset而不是DataTable,还是有一些方法可以使用DAO类来公开DataTable的成员?我不知道如何将DataTable.Columns转换为DAO.Fields,而不是遍历它们... – Karter 2013-03-12 15:44:54

+0

我想我只是不清楚当前DLL在数据库接口方面做了什么。 VBA应用程序是Excel还是Access? – 2013-03-12 16:01:26

回答

3

我刚刚在过去的3周里做了一些非常相似的事情。

我最终做两个.NET程序集:

  1. 是(由.NET应用程序使用)会谈中数据存储的纯.NET程序集。
  2. 包装第一个程序集并添加COM开销(ADODB引用和COM-Visible接口)的“COM Interop”程序集。

我使用VSTO“AddIn.Object”属性从Excel VBA调用第二个程序集。

我最终将System.Data.DataTables转换为ADODB.Recordsets,就像你提到的那样。让.NET和VBA谈论除基本类型之外的任何事情都让我感到沮丧。事实上,我最终将一些对象序列化为JSON,这样两个世界可以进行通信。

它确实看起来很疯狂,但我重新发明了车轮。


  • 我跟着这个MSDN文章使我的.NET代码调用由VBA。
  • 我用这个Code Project文章(我敢肯定你已经看过)转换为Recordset *。
  • 我让框架处理字符串,整数等
  • 因为我用Json.Net所有其它数据类型和一个自定义VBA类做的JSON序列化。

    *将文章转换为VB.Net并添加了一些额外的错误处理。

+0

一方面,我觉得答案确实是疯了。另一方面,我真的希望有更好的答案。你愿意/能够分享你创建的任何接口/转换代码吗? – Karter 2013-03-12 16:46:44

+0

我会用一些细节更新我的答案。 – nunzabar 2013-03-12 17:17:47

+0

+1对于一些非常疯狂的互操作性...感谢微软,我们感激不尽! – 2013-03-12 17:46:41

1

好的,这可能不是最优雅的(或完整的,在这一点上)解决方案;但我认为这是我要走的路线。

而不是将整个事情转换为ADODB Recordset(并复制任何迭代),我只是抛出DataTable类,并将自己的Recordset类写作通用数据读取器的COM包装器(通过IDataReader接口),并添加了一个新的Field类来管理类型转换并将Fields设置为Field的一个数组(自互操作性仇恨泛型)

它基本上创建一个只向前的ADODB记录集(相同的限制),但仅具有一次加载一行,所以大部分数据可以作为托管代码处理,直到你知道他们想要做什么(我将添加使用读取器的ToArray,ToAccessDB,ToFile等方法)同时仍然允许迭代Recordset f rom excel/access/vbscript/vb6(如果这真的是他们想要做的事情..主要需要传统的支持) 这里是一个例子,以防其他人必须再次这样做;稍作修改为简洁:

Public Interface IRecordset 
    ReadOnly Property CursorPosition As Integer 
    ReadOnly Property FieldCount As Integer 
    ReadOnly Property Fields As Field() 

    Function ReadNext() As Boolean 
    Sub Close() 
End Interface 

<System.ComponentModel.DesignerCategory("")> _ 
<ClassInterface(ClassInterfaceType.None)> _ 
<ComDefaultInterface(GetType(IRecordset))> _ 
<Guid("E7AFBBB6-CB20-44EC-9CD2-BC70B94CD8B7")> _ 
Public Class Recordset : Implements IRecordset : Implements IDisposable 
    Private _Reader = Nothing 
    Private _FieldCount As Integer = Nothing 
    Private _Fields() As Field 
    Public ReadOnly Property CursorPosition As Integer Implements IRecordset.CursorPosition... 
    Public ReadOnly Property FieldCount As Integer Implements IRecordset.FieldCount... 
    Public ReadOnly Property Fields As Field() Implements IRecordset.Fields... 

    Friend Sub Load(ByVal reader As IDataReader) 
     _Reader = reader 
     _FieldCount = _Reader.FieldCount 
     _Fields = Array.CreateInstance(GetType(DataColumn), _FieldCount) 
     For i = 0 To _FieldCount - 1 
      _Fields(i) = New Field(i, Me) 
     Next 
    End Sub 

    'This logic kinda sucks and is dumb. 
    Public Function ReadNext() As Boolean Implements IRecordset.ReadNext 
     _EOF = Not _Reader.Read() 
     If _EOF Then Return False 
     _CursorPosition += 1 
     For i = 0 To _FieldCount - 1 
      _Fields(i)._Value = _Reader.GetValue(i).ToString 
     Next 
     Return True 
    End Function 

从这里你只需要定义某种类型的像字段或列,并添加一个Interop包装为该类型:

Public Interface IField 
    ReadOnly Property Name As String 
    ReadOnly Property Type As String 
    ReadOnly Property Value As Object 
End Interface 

<System.ComponentModel.DesignerCategory("")> _ 
<ClassInterface(ClassInterfaceType.None)> _ 
<Guid("6230C670-ED0A-48D2-9429-84820DC2BE6C")> _ 
<ComDefaultInterface(GetType(IField))> _ 
Public Class Field : Implements IField 
    Private Reader As IDataReader = Nothing 
    Private Index As Integer = Nothing 
    Public ReadOnly Property Name As String Implements IField.Name 
     Get 
      Return Reader.GetName(Index) 
     End Get 
    End Property 
    Public ReadOnly Property Value As Object Implements IField.Value 
     Get 
      Return Reader.GetValue(Index) 
     End Get 
    End Property 
    Public ReadOnly Property Type As String Implements IField.Type 
     Get 
      Return Reader.GetDataTypeName(Index).ToString 
     End Get 
    End Property 
    Sub New(ByVal i As Integer, ByRef r As IDataReader) 
     Reader = r 
     Index = i 
    End Sub 
End Class 

所有这一切都是相当愚蠢,但它似乎运作良好。

注意:我现在只用了.Net大概4天,所以这可能很糟糕,请随时评论我可能做的非常愚蠢的事情。