2

我正在尝试创建一个.Net DLL,基本上作为数据库连接的抽象层;它将替换我们用 VB6 编写的当前 DLL,我正在尝试尽可能地匹配当前功能。

无论如何,我遇到的基本问题是我找不到一种方法来让像 DataColumnCollection 或 DataColumn 这样的 .Net 类显示在 VBA 解释器中——例如,它可能会说,类型为“MarshalByValueComponent”的“Column”, ”,但值为“无变量”。

如果我完全重新创建两个类(即字段作为字段的集合,它继承自 DataColumn,然后为两者定义一个接口),我可以让它工作,但这似乎增加了很多开销(应该是?)一个非常简单的想法。我觉得我只是在编组器处理 DataColumn 类的方式上遗漏了一些非常简单的东西。

我在网上找到的很多东西都是关于如何将 DataTable 或 DataReader 转换为旧版 ADODB 记录集,但这也会增加很多开销......我宁愿将它保留为 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

注意:我最初尝试将属性 Columns 作为返回 MyBase.Columns 的 DataColumnCollection。它以对象的形式出现,而不是 MarshalByValueComponent,但也是空的。我知道 MyBase.Column(0) 有一个值,因为我可以将 Msgbox(MyBase.Columns(0).ColumnName) 放在 get 中的 return 正上方,它会很好地弹出(不要判断;这比为此使用调试器)...

我不介意同时定义它们,但我不能继承 DataColumnCollection 并且 COM 接口在处理泛型方面已经很烂了。在不重新发明轮子的情况下,有没有其他方法可以解决这个问题?

谢谢你的帮助!

4

2 回答 2

3

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

我最终制作了两个 .NET 程序集:

  1. 与数据存储区对话的纯 .NET 程序集(供 .NET 应用程序使用)。
  2. 包装第一个程序集并添加 COM 开销(ADODB 引用和 COM-Visible 接口)的“COM 互操作”程序集。

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

正如你提到的,我最终将 System.Data.DataTables 转换为 ADODB.Recordsets。让 .NET 和 VBA 谈论除原始类型之外的任何内容对我来说非常令人沮丧。事实上,我最终将一些对象序列化为 JSON,以便两个世界可以通信。

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


  • 我按照这篇MSDN文章使我的 .NET 代码可由 VBA 调用。
  • 我使用此代码项目文章(我相信您已经看过)转换为 Recordset*。
  • 我让框架处理字符串、整数等。
  • 对于所有其他数据类型,我使用Json.Net和自定义 VBA 类来进行 JSON 序列化。

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

于 2013-03-12T16:23:56.927 回答
1

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

我没有将整个内容转换为 ADODB 记录集(并复制任何迭代),而是完全抛弃了 DataTable 类,并将我自己的 Recordset 类编写为通用数据读取器的 COM 包装器(通过 IDataReader 接口)并添加了一个新的 Field 类来管理类型转换并将 Fields 设置为 Field 数组(因为互操作讨厌泛型)

它基本上创建了一个只进的 ADODB 记录集(同样的限制),但它的好处是一次只加载一行,所以大部分数据可以作为托管代码处理,直到你知道他们想用它做什么(我'将为使用阅读器的 ToArray、ToAccessDB、ToFile 等添加方法),同时仍然允许从 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

从这里您只需要定义一些类型,如 Field 或 Column 并为该类型添加一个互操作包装器:

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 天,所以这可能很糟糕,请随时评论我可能做的任何极其愚蠢的事情。

于 2013-03-14T01:24:32.243 回答