Tedious: リクエストはLoggedIn状態でのみ行うことができ、SentClientRequest状態では行うことができません(コード:'EINVALIDSTATE')

作成日 2016年09月28日  ·  23コメント  ·  ソース: tediousjs/tedious

以下の例では、関数の配列をシリアルに実行しようとしています。 ただし、リクエストはLoggedIn状態でのみ行うことができるため、そのconnection.state !== connection.STATE.LOGGED_INを確認する必要がありました。 そして、以下の読み取り関数でわかるように、エラーを取り除くためにリクエストをキューに戻す必要がありました。

{ [RequestError: Requests can only be made in the LoggedIn state, not the SentClientRequest state]
  message: 'Requests can only be made in the LoggedIn state, not the SentClientRequest state',
  code: 'EINVALIDSTATE' }

これを達成するためのより良い方法はありますか? 同様の問題が#19#355の前に提起されたことがわかります。 接続で複数のリクエストを実行するための推奨される方法はありますか?

コード:

var Connection = require('tedious').Connection;  
var Request = require('tedious').Request;  
var TYPES = require('tedious').TYPES;  
var async = require('async');

// Create connection to database
var config = {
        userName: 'mylogin',  
        password: 'mypassword',  
        server: 'localhost',
        options: {
            // encrypt: true, /*If you are connecting to a Microsoft Azure SQL database, you will need this*/
            database: 'testdb'
        }
    }
var connection = new Connection(config);  

// Attempt to connect and execute queries if connection goes through
connection.on('connect', function(err) {  
    if (err) {
        console.log(err);
    }  
    else    {
        console.log("Connected");

        // Execute all functions in the array serially
        async.waterfall([
            function Start(callback){
                console.log("Starting...");
                callback(null, 'Jake', 'United States');
            },
            function Insert(name, location, callback){
                console.log("Inserting into Table...");

                request = new Request("INSERT dbo.employees (name, location) OUTPUT INSERTED.id VALUES (<strong i="14">@Name</strong>, @Location);", function(err){  
                     if (err) {
                            console.log(err);
                        }  
                    }
                );  
                request.addParameter('Name', TYPES.NVarChar, name);  
                request.addParameter('Location', TYPES.NVarChar, location);  

                request.on('row', function(columns) {  
                    columns.forEach(function(column) {  
                      if (column.value === null) {  
                        console.log('NULL');  
                      } else {  
                        console.log("Employee id inserted is " + column.value);  
                      }  
                    });  
                }); 

                // Check how many rows were inserted
                request.on('doneInProc', function(rowCount, more) {  
                    console.log(rowCount + ' row(s) inserted');  
                    callback(null, 'Jared');
                });             

                connection.execSql(request);  
            },
            function Read(callback){
                // Requests can only be made in the LoggedIn state, so check for that
                if (connection.state !== connection.STATE.LOGGED_IN) {
                    // Put the request back on the dispatcher if connection is not in LoggedIn state
                    setTimeout(Read, 0, callback);
                    return;
                }

                console.log("Reading rows from the Table...");

                // Create the request to read from table
                request = new Request("SELECT * FROM dbo.employees;", function(err) {  
                    if (err) {
                        console.log(err);
                    }  
                });  

                // Output the number of rows read 
                request.on('doneInProc', function (rowCount, more, rows) {  
                    console.log(rowCount + ' row(s) returned');  
                    callback(null);
                });  

                connection.execSql(request);  
            }],
            function Complete(err, result) {
                if(err) {
                    console.log("Error:", err);
                }
                else {
                    console.log("Done!");
                }
            }
        )
    }
});

最も参考になるコメント

グーグルからここに来る人々のために(私がしたように):退屈な接続プールはこの問題と戦うための素晴らしい方法を提供します。 pool.acquire((err, connection) => ...)connection.release()を使用すると、同時実行の問題を防ぐ簡単な方法です。 複数の接続が必要ない場合は、クエリを効果的にシリアル化するために{min: 1, max: 1}でプールを構成します。

全てのコメント23件

Requestで設定できる「callback」プロパティとRequestで「requestCompleted」イベントがあり、どちらも機能しているようです。 そして、それらは私たちが試しなかった2つのことです:)

これには、クリーンアップとドキュメントが必要になる場合があります。

これらを試して、機能するかどうかを確認してください。

'コールバック'の使用:

  request.callback = function (err, rowCount, rows) {
      // rows is not being set but rowCount is. May be a bug.
      if (err) {
        // Error handling.
      } else {
        // Next SQL statement.
      }
  };

'requestCompleted'の使用:

  request.on('requestCompleted', function () {
      // Next SQL statement.
  });

このメモを実際にhttps://github.com/tediousjs/tedious/issues/61に入れて、同じ問題なのでこれを閉じます。

問題を再開します。 https://github.com/tediousjs/tedious/issues/61は完全に無関係です。 @SaloniSonpalによって参照された問題は実際にはクローズされています。 したがって、追跡のためにこれを再度開きます。

これには、クリーンアップとドキュメントが必要です。

@tvrprasadに感謝します。 これらは両方とも機能し、はるかにクリーンです。 callbackアプローチには、エラーを処理するという利点があります。 Event: 'requestCompleted'間違いなく文書化する必要があります。

(私の電話から送信します、簡潔さを許してください)。

コールバックプロパティを設定すると、渡したコールバックが上書きされます
Requestコンストラクターに。 代わりに、からコールバックを呼び出す必要があります
Requestコンストラクターに渡されるコールバック内の非同期。 このようにあなたは
エラーを非同期に正しく転送することもできます。

ああ、コールバックが渡されたコールバックに設定されていることを見逃しました。したがって、正しい方法は次のようになります。

  request = new Request(sqlQuery, function (err, rowCount, rows) {
    if (err) {
      // Error handling.
    } else {
      // Next SQL statement.
    }
  });

@arthurschreiber'requestCompleted 'イベントの理由は何ですか? 冗長なようです。 私が気付いたもう1つのことは、コールバックに渡された行パラメーターが常に空であることです。 有効なデータが含まれるシナリオはありますか? ドキュメントを更新します。

私が気付いたもう1つのことは、コールバックに渡された行パラメーターが常に空であることです。 有効なデータが含まれるシナリオはありますか? ドキュメントを更新します。

接続時に指定できるrowCollectionOnRequestCompletionオプションがあります。 グローバルオプションとしてそれを持っていることは少し奇妙です、私はこのリクエストごとに持っている方が理にかなっていると思います。

この背後にある主な考え方は次のとおりです。 rowCollectionOnRequestCompletionオプションが有効になっている場合、対応するRequestが完了するまで行がメモリに保存されます(現在、 Requestまで保持されていると思います) んが、結果セットが大きい場合は、パフォーマンスに大きな悪影響を与える可能性があります。 推奨されるアプローチは、 rowsコールバックを使用することです。すべての行を配列に収集する必要がある場合は、自分で行うことができます。

これは最適ではないと思います。接続ごとではなく、この構成可能なリクエストごとの方がおそらく良いでしょう。しかし、 Requestコンストラクターはまだオプションを受け取らないため、メソッドのシグネチャは次のようになります。かわった。

'requestCompleted'イベントの理由は何ですか?

よくわかりません。これは、面倒な作業を始める前に追加されたものです。

rowCollectionOnRequestCompletionの問題を追跡するために#459を開きました。 私たちはそれを追加して処理できると確信しています
下位互換性のある方法で。

コールバックプロパティを設定すると、渡したコールバックが上書きされます
Requestコンストラクターに。 代わりに、からコールバックを呼び出す必要があります
Requestコンストラクターに渡されるコールバック内の非同期。 このようにあなたは
エラーを非同期に正しく転送することもできます。

@arthurschreiberその通りです。 これは、結果/エラーを上書きしない正しい方法です。 魅力のように機能します:-)

Event: 'requestCompleted'のドキュメントを改善するために#464を作成しました

グーグルからここに来る人々のために(私がしたように):退屈な接続プールはこの問題と戦うための素晴らしい方法を提供します。 pool.acquire((err, connection) => ...)connection.release()を使用すると、同時実行の問題を防ぐ簡単な方法です。 複数の接続が必要ない場合は、クエリを効果的にシリアル化するために{min: 1, max: 1}でプールを構成します。

誰でも提案してください、Windows認証モードはnodejs2Sql接続では機能していません。

@wafaabbass tediousは、Windows統合認証をまだサポートしていません。 現在作業中です。#415で追跡できます。

ねえ、みんな、私は同じ問題を抱えています。

デバッグ:

リクエストは、SentClientRequest状態ではなく、LoggedIn状態でのみ行うことができます。
(ノード:5180)UnhandledPromiseRejectionWarning:未処理のプロミス拒否(拒否ID:1):RequestError:リクエストはLoggedIn状態でのみ行うことができ、SentClientRequest状態では行うことができません。
(ノード:5180)[DEP0018]非推奨警告:未処理のプロミス拒否は非推奨になりました。 将来、処理されないpromise拒否は、ゼロ以外の終了コードでNode.jsプロセスを終了します。

まだ解決しましたか? これは私のコードです。

function createAsPromiseWithTransaction(callback, data, connection) {

  const transactionReady = (err) => {
    if (err) {
      return callback(err)
    }
    return callback(null)
  }

  const transactionFailed = (originalErr) =>
    (err) => {
      if (err) {
        return callback(err)
      }
      return callback(originalErr)
    }

  const willIGetArea = new Promise(
    (resolve, reject) => {
      const create = function(data, connection) {
        let areaIdInserted = null
        const request = new Request(CONSTANTS.SPNames.Area,
          (err, rowCount, rows) => err ? reject(err) : resolve(areaIdInserted))

        request.addParameter('IUser', TYPES.UniqueIdentifier, data.iUser)
        request.addParameter('ParentArea', TYPES.Int, data.parentArea)
        request.addParameter('Name', TYPES.VarChar, data.nameArea)
        request.addParameter('ShortName', TYPES.VarChar, data.shortNameArea)
        request.addParameter('Description', TYPES.VarChar, data.descriptionArea)
        request.addParameter('Gender', TYPES.Bit, data.gender)
        request.addParameter('Colour', TYPES.VarChar, data.colour)
        request.addParameter('X', TYPES.Int, data.x)
        request.addParameter('Y', TYPES.Int, data.y)
        request.addParameter('Active', TYPES.Int, data.active)

        request.on('row', function(columns) {
          areaIdInserted = columns[1].value
        });

        connection.callProcedure(request)
      }

      const transactionStarted = function(err) {
        if (err) return callback(err)
        create(data, connection)
      }

      connection.beginTransaction(transactionStarted)
    })

  const willIGetSymptom = function(areaId) {

    return new Promise(function(resolve, reject) {

      const create = function(data, connection, areaId) {
        let symptomIdInserted = null
        const request = new Request(CONSTANTS.SPNames.Symptom,
          (err, rowCount, rows) => err ? reject(err) : resolve(symptomIdInserted))

        request.addParameter('IUser', TYPES.UniqueIdentifier, data.iUser)
        request.addParameter('AreaId', TYPES.BigInt, areaId)
        request.addParameter('Code', TYPES.VarChar, data.code)
        request.addParameter('Name', TYPES.VarChar, data.nameSymptom)
        request.addParameter('ShortName', TYPES.VarChar, data.shortNameSymptom)
        request.addParameter('Description', TYPES.VarChar, data.descriptionSymptom)
        request.addParameter('Gender', TYPES.Bit, data.gender)
        request.addParameter('Active', TYPES.Bit, data.active)

        request.on('row', function(columns) {
          symptomIdInserted = columns[1].value
        });


        connection.callProcedure(request)
      }

      create(data, connection, areaId)

    })
  }

  const willIGetModifier = new Promise(function(resolve, reject) {

    const request = new Request(
      CONSTANTS.SPNames.Modifier,
      function(err, rowCount, rows) {
        if (err) {
          reject(err)
        } else {
          resolve(rowCount,rows)
        }
      })

    request.addParameter('IUser', TYPES.UniqueIdentifier, data.iUser)
    request.addParameter('Name', TYPES.VarChar, data.nameModifier)
    request.addParameter('Order', TYPES.SmallInt, data.order)
    request.addParameter('Active', TYPES.Bit, data.active)

    connection.callProcedure(request)

  })

  const runPromise = () =>
    willIGetArea
    .then( (areaId) =>
      willIGetSymptom(areaId)
      .then( (symptomId) =>
        willIGetModifier
        .then((rowCount,rows) => connection.commitTransaction(transactionReady))
        .catch((err) => connection.rollbackTransaction(transactionFailed(err)))
      )
      .catch((err) => connection.rollbackTransaction(transactionFailed(err)))
    )it 
    .catch((err) => connection.rollbackTransaction(transactionFailed(err)))

  runPromise()

}

willIGetAreaとwillIGetSymptomを呼び出すと、これはすべて機能しますが、willGetModifierを使用して呼び出すと、同じ問題が発生します。 どうもありがとう。

グーグルがこのエラーメッセージのために最初にこのページを返すので、誰かがそれが役に立つと思う場合に備えて。
私の単一接続ベースのサービスでは、接続のPromiseベースのラッパーを作成することができました。 私は経験豊富なjs開発者ではないので、一般的に良い解決策かどうかはわかりません。

class PromisedConnection {
    constructor(config) {
        // this.connection always points to a promise resolving after the last assigned request
        // initial setting is either resolved once a new connection is established or rejected if an error occurs
        this.connection = new Promise((resolve, reject) => {
            const dbConnection = new Connection(config);
            dbConnection.on("connect", function (err) {
                if (err) {
                    reject(err);
                }
                else {
                    resolve(dbConnection);
                }
            });
        });
    }

    execute(request) {
        const nextConnection = new Promise((resolve, reject) => {
            // after scheduling new request this.connection should be reassigned to be the last in promise queue
            this.connection
                .catch( (reason) => {
                    reject(reason);
                } )
                .then( (dbConnection) => { // a new request can be executed only within the connection is free again (resolved after the last request)
                    request.on("requestCompleted", () => { // add an additional event listener in order to release connection after the request is done
                        resolve(dbConnection);
                    });
                    dbConnection.execSql(request);
                });
        } );
        this.connection = nextConnection;
    }
}

そして、クエリ自体は次のように実行できます。

const dbConnection = new PromisedConnection(config);

function getObjList(query, parameters = []) {
    return new Promise((resolve, reject) => {
        const objList = [];
        let request = new Request(
            query,
            function (err, rowCount, rows) {
                if (err) {
                    reject(err);
                } else if (rowCount < 1) {
                    reject(new Error("0 rows returned from DB"));
                }
            }
        );
        for (const {name, type, value} of parameters) {
            request.addParameter(name, type, value);
        };

        request.on("row", (columns) => {
            objList.push(Obj.fromColumns(columns)); // here I just make a specific object from each row
        });

        request.on("requestCompleted", () => {
            resolve(objList);
        });

        dbConnection.execute(request);
    });
}

そのため、 getObjListは、接続状態をチェックせずにいつでも実行できるようになりました。

ここでの懸念はよくあります。 私のコードでは、接続要求、SQL要求、そして挿入要求を実行します。 SQLリクエストの後にrequestCompletedを待った後でも、SQLと挿入リクエストの間で別の接続リクエストを実行しない限り、挿入リクエストでエラーが発生します。

このエラーの一般的な理由は、_一度に1つの接続で実行できるクエリは1つだけ_であるということです。 別のリクエストを行う前に、エラーまたは結果のいずれかでリクエストコールバックが実行されるまで待つ必要があります。 こちらのFAQページをご覧ください

このフォーラムには色々な問題が多すぎるようですので。 エラーRequests can only be made in the LoggedIn state, not the SentClientRequest stateは、さまざまな理由で発生する可能性があります。 このエラーが発生し、FAQページが役に立たなかった場合は、問題にさらに正確に対処できるように、次の情報を使用して独自の問題を提起してください。

  • 面倒なバージョン
  • 接続の構成
  • 再現可能なスクリプト

ありがとう! 😄

これを解決することができました、問題は私の側にありました、あなたがリクエストごとに別々の接続が必要であることに気づいていませんでした

@mgarfあなたの解決策は何でしたか? また、リクエストごとに個別の接続が必要であることに気づきませんでした。 このディスカッションの他の人と同じように、並行性の問題が発生しています。

@mikebutakリクエストごとに個別の接続が必要なわけではありません。 必要なのは、一度に1つのリクエストを処理する1つの接続だけです。 1つの接続で複数のリクエストを処理するには、前のリクエストのコールバックが終了した後、そのコールバックで新しいリクエストを実行する必要があります。 例えば、

function executeStatement() {
  request = new Request("select 42, 'hello world'", function(err, rowCount) {
    if (err) {
      console.log(err);
    } else {
      console.log(rowCount + ' rows');
    }

    connection.execSql(new Request("select 42, 'hello world'", function(err, rowCount) {
      if (err) {
        console.log(err);
      } else {
        console.log(rowCount + ' rows');
      }
      connection.close();
    }))
  });
  connection.execSql(request);

この問題のコールバックを使用して完全なソリューションを投稿しています。 私は同じ問題を抱えていました、そしてグーグルを見たとき私はここに行き着きました。 このコードを、毎秒1回のhttp呼び出しを実行するスクリプトを使用してロードテストしました。また、問題なく0.5秒実行しました。

const {Connection, Request} = require("tedious");

const executeSQL = (sql, callback) => {
  let connection = new Connection({
    "authentication": {
      "options": {
        "userName": "USERNAME",
        "password": "PASSWORD"
      },
      "type": "default"
    },
    "server": "SERVER",
    "options": {
      "validateBulkLoadParameters": false,
      "rowCollectionOnRequestCompletion": true,
      "database": "DATABASE",
      "encrypt": true
    }
  });

  connection.connect((err) => {
    if (err)
      return callback(err, null);

    const request = new Request(sql, (err, rowCount, rows) => {
      connection.close();

      if (err)
        return callback(err, null);

      callback(null, {rowCount, rows});
    });

    connection.execSql(request);
  });
};

executeSQL("SELECT * FROM users", (err, data) => {
  if (err)
    console.error(err);

  console.log(data.rowCount);
});

//or

executeSQL("SELECT * FROM users", (err, {rowCount, rows}) => {
  if (err)
    console.error(err);

  console.log(rowCount);
});

この問題のコールバックを使用して完全なソリューションを投稿しています。 私は同じ問題を抱えていました、そしてグーグルを見たとき私はここに行き着きました。 このコードを、毎秒1回のhttp呼び出しを実行するスクリプトを使用してロードテストしました。また、問題なく0.5秒実行しました。

const {Connection, Request} = require("tedious");

const executeSQL = (sql, callback) => {
  let connection = new Connection({
    "authentication": {
      "options": {
        "userName": "USERNAME",
        "password": "PASSWORD"
      },
      "type": "default"
    },
    "server": "SERVER",
    "options": {
      "validateBulkLoadParameters": false,
      "rowCollectionOnRequestCompletion": true,
      "database": "DATABASE",
      "encrypt": true
    }
  });

  connection.connect((err) => {
    if (err)
      return callback(err, null);

    const request = new Request(sql, (err, rowCount, rows) => {
      connection.close();

      if (err)
        return callback(err, null);

      callback(null, {rowCount, rows});
    });

    connection.execSql(request);
  });
};

executeSQL("SELECT * FROM users", (err, data) => {
  if (err)
    console.error(err);

  console.log(data.rowCount);
});

//or

executeSQL("SELECT * FROM users", (err, {rowCount, rows}) => {
  if (err)
    console.error(err);

  console.log(rowCount);
});

このソリューションは機能しますが、有効な実装ですか? 何十万もの挿入/選択があり、それぞれに接続を作成する可能性があることを考えると、それはどの程度実現可能ですか? ところで解決策をありがとう、私はそれが実行可能な解決策であるかどうかを理解しようとしています。 特にAzure関数を使用している場合はどうでしょうか。

このページは役に立ちましたか?
0 / 5 - 0 評価

関連する問題

ghost picture ghost  ·  5コメント

jstephens7 picture jstephens7  ·  5コメント

ggazulla picture ggazulla  ·  4コメント

arthurschreiber picture arthurschreiber  ·  8コメント

GuyHarwood picture GuyHarwood  ·  7コメント