2

基本的にデータベース接続の抽象化レイヤーとして.Net DLLを作成しようとしています。VB6 で記述された現在の DLL を置き換える予定であり、現在の機能に可能な限り一致させようとしています。

とにかく、私が抱えている本質的な問題は、DataColumnCollection や DataColumn などの .Net クラスを取得して VBA インタープリターに表示する方法が見つからないことです。 」ですが、値は「変数なし」になります。

両方のクラスを完全に再作成すれば動作させることができます (つまり、DataColumn から継承するフィールドのコレクションとして Fields を作成し、両方のインターフェイスを定義します)。 ?) かなり単純なアイデアです。マーシャラーが DataColumn クラスを処理する方法で、非常に単純なものが欠けているように感じます。

私がオンラインで見つけているものの多くは、DataTable または DataReader を従来の ADODB Recordset に変換する方法に関するものですが、それによっても多くのオーバーヘッドが追加されます... DataTable のままにして COM インターフェイスを作成したいと思いますVBA がそれと対話できるようにします。そうすれば、たとえば、テーブルを Excel シートに書き込みたい場合、作業を複製することはありません (ADODB レコードセットに変換してから、Excel シートに読み書きする必要があります。テーブル全体を 2 回繰り返す必要があります。 ..)

本の長さの説明で申し訳ありません-根本的な原因はレガシー機能と一致させようとしているため、問題を少し明確にする必要があると感じました。動作しない現在のインターフェイスの例を次に示します。

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

注: 最初に、MyBase.Columns を返す DataColumnCollection としてプロパティ Columns を試しました。これは、MarshalByValueComponent ではなくオブジェクトとして渡されましたが、空でもありました。Msgbox(MyBase.Columns(0).ColumnName) を get の return のすぐ上に置くことができるので、MyBase.Column(0) に値があることはわかっています。これにはデバッガを使用します)...

両方を定義するだけでもかまいませんが、DataColumnCollection を継承することはできず、COM インターフェイスはジェネリックを扱うのが苦手です。車輪を再発明することなく、これを回避する他の方法はありますか?

ご協力いただきありがとうございます!

4

2 に答える 2

3

私はこの 3 週間、不気味なほど似たようなことをしていました。

最終的に 2 つの .NET アセンブリを作成しました。

  1. データストアと通信する純粋な .NET アセンブリ (.NET アプリで使用するため)。
  2. 最初のアセンブリをラップし、COM オーバーヘッド (ADODB 参照と COM-Visible インターフェイス) を追加する "COM Interop" アセンブリ。

VSTO の "AddIn.Object" プロパティを使用して、Excel VBA から 2 番目のアセンブリを呼び出します。

あなたが言及したように、私は System.Data.DataTables を ADODB.Recordsets に変換することになりました。.NET と VBA がプリミティブ型以外の何かについて話すようになるのは、私にとって非常に苛立たしいことでした。実際、いくつかのオブジェクトを JSON としてシリアライズして、2 つの世界が通信できるようにしました。

正気ではないように見えますが、私は車輪を再発明しました。


  • このMSDNの記事に従って、.NET コードを VBA で呼び出せるようにしました。
  • このコード プロジェクトの記事 (ご覧になったことがあると思います) を使用して、Recordset* に変換しました。
  • フレームワークに文字列や整数などを処理させます。
  • 他のすべてのデータ型については、 Json.Netとカスタム VBA クラスを使用して JSON シリアル化を行いました。

    *記事を VB.Net に変換し、エラー処理を追加しました。

于 2013-03-12T16:23:56.927 に答える
1

さて、これはおそらく最もエレガントな(または現時点では完全な)ソリューションではありません。でもそれが私が行くルートだと思います。

全体をADODBRecordsetに変換する(そして反復を複製する)代わりに、DataTableクラスを完全に破棄し、(IDataReaderインターフェイスを介して)汎用データリーダーのCOMラッパーとして独自のRecordsetクラスを作成しました。型変換を管理し、FieldsをFieldの配列として設定するための新しいFieldクラス(相互運用機能はジェネリックスを嫌うため)

基本的に、転送専用のADODBレコードセット(同じ制限)を作成しますが、一度に1行しかロードしないという利点があるため、データの大部分は、データをどのように処理するかがわかるまで、マネージコードとして処理できます(Iリーダーを使用するToArray、ToAccessDB、ToFileなどのメソッドを追加しますが、Excel / access / vbscript / vb6からRecordsetを反復処理する機能を許可します(それが本当に必要な場合)。とにかくレガシーサポートの場合)これは、他の誰かがこれを再度実行する必要がある場合の例です。簡潔にするために多少変更:

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 に答える