Access (VBA)

Access VBAに関するフォーラムです。
  • 解決済みのトピックにはコメントできません。
このトピックは解決済みです。
質問

 
(Windows 10 Pro : Microsoft 365)
「このコンテキストで操作は許可されていません」とエラーが出る
投稿日時: 22/11/18 11:43:47
投稿者: rose11

お世話になります。まだまだACCESS初心者です。
以下のコードで3点ご教示いただきたく、よろしくお願いいたします。
 
(1)※1のところで、rsとrstを使いまわしていますが、これはあまり良くないでしょうか。
  正しいコードは別のレコードセットを宣言して使う、でしょうか。
 
(2)エラー処理を確認する為、※2で、「T_Table!社員コード」を「T_Table!社員コ」にした時、
  ※3の「rst.Close」で「このコンテキストで操作は許可されていません」とエラーが出ます。
  rstはopen状態なのにcloseできないのですが、どうしたら良いでしょうか。
 
(3)変数の付け方やロジックの書き方など、こういうのが慣例だよ、があれば
  ご教示いただけると助かります。
 
 
お手数をおかけして申し訳ありませんが、どうぞよろしくお願いいたします。
 
 
===========================================================================
Private Sub cmdRegist_Click()
    Dim CN As New ADODB.Connection
    Dim rs As New ADODB.Recordset
    Dim rst As New ADODB.Recordset
     
On Error GoTo RegistError
 
    Set CN = CurrentProject.Connection
    CN.CursorLocation = adUseClient
     
    Set rs = New ADODB.Recordset
    Set rst = New ADODB.Recordset
     
    rs.Open "T_test1", CN, adOpenKeyset, adLockOptimistic
    rst.Open "T_test2", CurrentProject.Connection, , adLockOptimistic
     
    Do Until rs.EOF
  
        Call RegistTest(rst, rs!社員コード.Value)    
        rs.MoveNext
         
    Loop
 
    rs.Close
    rst.Close
     
    rs.Open "T_test3", CurrentProject.Connection '※1
    rst.Open "T_test4", CurrentProject.Connection, , adLockOptimistic
 
    Do Until rs.EOF
     
        処理
        rs.MoveNext
 
    Loop
 
    GoTo EndProc
     
    Exit Sub
'-----------------------------------------------------------------------------
RegistError:
 
    MsgBox Err.Description, vbCritical, strMsgTitle
 
     
EndProc:
    If rs.State = adStateOpen Then rs.Close: Set rs = Nothing
    If rst.State = adStateOpen Then rst.Close: Set rst = Nothing '※3
    CN.Close: Set CN = Nothing
     
End Sub
 
'************************************************************************
Private Sub RegistTest(T_Table As ADODB.Recordset, strEmpCode As String)
 
    T_Table.AddNew
    T_Table!社員コード = strEmpCode        '※2
    T_Table.Update
 
End Sub
 
===========================================================================
 

回答
投稿日時: 22/11/18 13:47:38
投稿者: sk

引用:
(1)※1のところで、rsとrstを使いまわしていますが、これはあまり良くないでしょうか
  正しいコードは別のレコードセットを宣言して使う、でしょうか。

一般論としての話なら「場合によるので正解はない」、
例示されたコードに関しての話なら「処理の結果や
ロジックに影響しないならどっちでもいい」という
回答になります。
 
引用:
(2)エラー処理を確認する為、※2で、「T_Table!社員コード」を「T_Table!社員コ」にした時、
  ※3の「rst.Close」で「このコンテキストで操作は許可されていません」とエラーが出ます

引用:
T_Table.AddNew
T_Table!社員コード = strEmpCode '※2
T_Table.Update

AddNew メソッドが呼び出された後、その直後のステートメントを
実行しようとした際に実行時エラーが発生したことにより
Update メソッドが呼び出されぬままプロシージャを抜け、
更にその状態のまま Close メソッドを呼び出そうとしたためです。
 
Close メソッド (ADO) :
https://learn.microsoft.com/ja-jp/sql/ado/reference/ado-api/close-method-ado?view=sql-server-ver16
 
Microsoft さんの引用:
即時更新モードの間に編集が進行中の場合、Close メソッドを呼び出すとエラーが生成されます。
その代わりに、最初に Update または CancelUpdate メソッドを呼び出してください。

 
引用:
(3)変数の付け方やロジックの書き方など、こういうのが慣例だよ、があれば
  ご教示いただけると助かります。

コーディング全般の話としてなら、その手の話題は宗教論争になりかねませんので
「とりあえず『リーダブルコード』でも読みなはれ」という提案に留めておきます。
 
例示されたコードに関して言えば、いくつか修正の余地があるでしょう。

投稿日時: 22/11/18 15:39:35
投稿者: rose11

sk 様
早々のご教示をありがとうございます。m(__)m
 

引用:
一般論としての話なら「場合によるので正解はない」、
例示されたコードに関しての話なら「処理の結果や
ロジックに影響しないならどっちでもいい」という
回答になります。

ありがとうございます。勉強になりました。
 
引用:
AddNew メソッドが呼び出された後、その直後のステートメントを
実行しようとした際に実行時エラーが発生したことにより
Update メソッドが呼び出されぬままプロシージャを抜け、
更にその状態のまま Close メソッドを呼び出そうとしたためです。

ありがとうございます。理由がよく解りました。
 
引用:
その代わりに、最初に Update または CancelUpdate メソッドを呼び出してください。

 
'************************************************************************
Private Sub RegistTest(T_Table As ADODB.Recordset, strEmpCode As String)
  
    T_Table.AddNew
    T_Table.Update
    T_Table!社員コード = strEmpCode '※2
    T_Table.Update
  
End Sub
 
こういうことでしょうか?
これでも同じエラーが出るので、解釈が違うでしょうか。
 
引用:
コーディング全般の話としてなら、その手の話題は宗教論争になりかねませんので
「とりあえず『リーダブルコード』でも読みなはれ」という提案に留めておきます。

ご教示ありがとうございます!
 
引用:
例示されたコードに関して言えば、いくつか修正の余地があるでしょう。

自分なりに考えた修正箇所は例えば、
・「Private Sub RegistTest・・・」にもエラー処理を施す
・「Private Sub RegistTest(T_Table As ADODB.Recordset, strEmpCode As String)」
 を
  「Private Function RegistTest(T_Table As ADODB.Recordset, strEmpCode As String) as Boolean」
 にして、呼び出し元は
If RegistTest(rst, rs!社員コード.Value) = False Then strErrorMsg="エラー:RegistTest" : GoTo catchError
 
にする、でした。
もしよろしければ修正箇所のヒントをご教示いただけますと幸いです。
 
重ね重ねお世話をおかけします。
どうぞよろしくお願いいたします。
 
 
 

回答
投稿日時: 22/11/18 18:14:41
投稿者: sk

引用:
Private Sub RegistTest(T_Table As ADODB.Recordset, strEmpCode As String)
   
    T_Table.AddNew
    T_Table.Update
    T_Table!社員コード = strEmpCode '※2
    T_Table.Update
   
End Sub

その Update メソッドはむしろ余計です。
 
とりあえず、標準モジュールに次のようなプロシージャを追加して下さい。
 
(標準モジュール)
------------------------------------------------------------
'レコードセットの解放をより細かく行なうサブルーチン(あくまでサンプル)
Sub ReleaseRecordset(Recordset As ADODB.Recordset)
 
    'オブジェクトを参照していなければ
    If Recordset Is Nothing Then
        'プロシージャを抜ける
        Exit Sub
    End If
     
    With Recordset
        'レコードセットが開かれている場合
        If .State = adStateOpen Then
            '新規レコードが挿入されたか、カレントレコードの編集中である場合
            If .EditMode And (adEditAdd + adEditInProgress) <> 0 Then
                '変更を全てキャンセルする
                .CancelUpdate
            End If
            'レコードセットを閉じる
            .Close
        End If
    End With
     
    '参照を解放
    Set Recordset = Nothing
 
End Sub
------------------------------------------------------------
 
引用:
EndProc:
    If rs.State = adStateOpen Then rs.Close: Set rs = Nothing
    If rst.State = adStateOpen Then rst.Close: Set rst = Nothing

そして上記のステートメントを次のように書き換えてみて下さい。
 
------------------------------------------------------------
 
EndProc:
    Call ReleaseRecordset(rs)
    Call ReleaseRecordset(rst)
 
------------------------------------------------------------

投稿日時: 22/11/19 09:19:48
投稿者: rose11

sk 様
 
早々のご回答をありがとうございます!
そしてご親切に詳細なご説明を本当にありがとうございます。勉強になります。
会社の環境でないと確認できないため、月曜日に結果をご報告させていただきます。
まずはお礼まで。。。

回答
投稿日時: 22/11/20 15:58:56
投稿者: ニャッチュ

提示されているVBAコードを見る限りでは
  
処理
 
の部分がブラックボックスになっていて怪しさを醸し出してるのですが
いかがでしょうか。

回答
投稿日時: 22/11/21 03:39:45
投稿者: MMYS

rose11 さんの引用:

(3)変数の付け方やロジックの書き方など、こういうのが慣例だよ、があれば
  ご教示いただけると助かります。

rose11さんが、初心者と自覚されているうちは、あまり気にする必要はないと思いますが、私だったら、下記のように書きます。なお、動作検証していません。そのままでは動かいないと思います。なお、私は普段、SQLで操作しており、ADOオブジェクトの直接アクセスは不備があると思います。あくまでロジック・コーディング規約の視点です。
 
Option Explicit

Const msEmployeeCode    As String = "社員コード"
Const tbTable1          As String = "T_test1"
Const tbTable2          As String = "T_test2"
Const tbTable3          As String = "T_test3"
Const tbTable4          As String = "T_test4"

Private Sub cmdRegist_Click()
    RegistNewRecord
End Sub

Private Sub RegistNewRecord()
'新規レコード登録
    Dim CN As New ADODB.Connection
    Dim strErrorMessage As String
        
    On Error GoTo ErrorTrap
    
    strErrorMessage = ""
    Set CN = CurrentProject.Connection
    CN.CursorLocation = adUseClient
    
    CN.BeginTrans
    
    strErrorMessage = AddRecordAtoB(tbTable1, tbTable2, CN)
    If strErrorMessage <> "" Then GoTo ErrorTrap
    
    strErrorMessage = AddRecordAtoB(tbTable3, tbTable4, CN)
    If strErrorMessage <> "" Then GoTo ErrorTrap
    
    CN.CommitTrans
    CN.Close
    Set CN = Nothing
    Exit Sub

ErrorTrap:
    If strErrorMessage = "" Then
        MsgBox Err.Description, vbCritical
    Else
        MsgBox strErrorMessage, vbCritical
    End If
        
    CN.RollbackTrans
    CN.Close
End Sub

Private Function AddRecordAtoB(strTableA As String _
                             , strTableB As String, CN As ADODB.Connection) As String
'レコード追加 A⇒B
'成功時は""を返す。エラー時はエラーメッセージを返す。
    Dim rsA As New ADODB.Recordset
    Dim rsB As New ADODB.Recordset
    Dim strEmployeeCode As String

    On Error GoTo ErrorTrap

    Set rsA = New ADODB.Recordset
    Set rsB = New ADODB.Recordset

    rsA.Open strTableA, CN, adOpenKeyset, adLockOptimistic
    rsB.Open strTableB, CurrentProject.Connection, , adLockOptimistic

    Do Until rsA.EOF
       strEmployeeCode = rsA.Fields(msEmployeeCode).Value
       With rsB
            .AddNew
            .Fields(msEmployeeCode) = strEmployeeCode
            .Update
        End With
        rsA.MoveNext
    Loop

    rsA.Clone
    rsB.Clone
    Set rsA = Nothing
    Set rsB = Nothing

    AddRecordAtoB = ""
    Exit Function

ErrorTrap:
    rsA.Close
    rsB.Close
    Set rsA = Nothing
    Set rsB = Nothing

    AddRecordAtoB = Err.Description
End Function

 
・コードは第三者が読む事を前提に書きましょう。
・コードは可読性を考えてて書きます。
・コメントなしでも、作成者の意図が伝わるのがベスト。
・コードはメンテナンスされるもの。簡単に修正できるようにします。例えば、テーブル名やフィールド名は変更される。と考なければなりません。
 
・ハードコーディングはご法度
業務は常に変化します。仕様変更に備え、フィールド名などは定数にして、定数でアクセスします。
例)
T_Table!社員コード = strEmpCode
  ↓
Const msEmployeeCode As String="社員コード"
T_Table.Fields(msEmployeeCode).Value = strEmpCode
 
・プロシージャ名は、名前だけで、処理内容が推測できること。
・プロシージャ名は動詞で始める
・変数名は、名前だけで、作成者の意図が理解できること。
・名前は省略しない。英和辞書で意味が分かるようにする。
 
例) strEmpCode
str と Code は作成者の意図が理解出来ます。しかし、Empは第三者に作成者の意図が理解出来ません。
 
またプロシージャ名は右上のコンボボックスでリストが出ます。このとき、アルファベット順に並びます。そしてコードは Ctrl+Space でインテリセンスが働きます。ですので、それを踏まえてプロシージャ名を決めます。
 
・引数は重要度の高いものを先、重要度の低いものを後。
第三者がコードを読む時、コードの先頭から読みます。重要な引数が後方にあると、理解が遅れます。
 
例) Sub RegistTest(T_Table As ADODB.Recordset, strEmpCode As String)
 
呼び出し時、社員コードが常に変化するけど、レコードセットは変化しません。常に変化する社員コードを先に書いたほうが、第三者に作成者のコードの意図が伝わります。
 
・プロシージャは単体テストできるようにする。
・cmdRegist_Click()のコードできるだけ単純にする。(呼出元と実行部を分離)
 
 
あと、更新系のコードはトランザクションが必須です。予測不能のエラーに備えましょう。特に業務でデータ破損は大問題です。
 
 
コーディング規約は、考え方が色々あります。
他にも注意点はありますが、重要なのは、作成者が仮に rose11さん 一人であっても、第三者に分かるように書くことです。なぜなら、業務内容の変化はよくあること。上司から修正依頼が来ます。でも、その時、当時なぜそのように書いたかコードの意図は忘れてますから。
 
 

投稿日時: 22/11/21 10:10:07
投稿者: rose11

sk 様
 
返信が遅くなり申し訳ございませんでした。
ご教示いただきました方法でうまくいきました!
早々に色々とご教示いただきまして、本当にありがとうございました。
助かりましたm(_ _)m

投稿日時: 22/11/21 10:19:49
投稿者: rose11

ニャッチュ 様
 
コメントをありがとうございます。
「処理」の部分は問題がないため、割愛させていただきました次第でした。
分かりづらい表現で失礼いたしました。m(_ _)m

投稿日時: 22/11/21 10:32:06
投稿者: rose11

sk 様
 
貴重なお時間を使ってご教示いただき、本当にありがとうございました!m(_ _)m

投稿日時: 22/11/21 11:24:21
投稿者: rose11

MMYS 様
 
ご教示をありがとうございます。
まさに私に足りないところだと思いました。
詳細なロジックをご提示いただき、解りやすいですし、
また要点も記載いただき、これから何に気を付ければ良いかが理解できるので助かります。
 
貴重なお時間を使ってのご教示、本当にありがとうございました!m(_ _)m