OracleCommand mit OracleDependency für immer warten

Hinweis Relevante Frage überHie hat keine Lösung

Denken Sie daran, dass ich kein Experte für Oracle oder die Programmierung gegen Oracle bin. Dies ist meine Testumgebung. Ich habe eine einzelne Tabelle im Schema STVM mit dem Namen STVM_NOTIFICATION. So sieht es aus:

CREATE TABLE STVM_NOTIFICATION
(   
    "ID"              NUMBER              NOT NULL, 
    "PROPERTYNAME"    VARCHAR2(16 BYTE)   NOT NULL, 
    "PROPERTYVALUE"   VARCHAR2(16 BYTE)   NOT NULL, 
    "ACTION"          VARCHAR2(32 BYTE)   NOT NULL, 
    "POSTDATE"        TIMESTAMP (6)       NOT NULL, 
    "SENT"            CHAR(1 BYTE)        NOT NULL,
    ADD CONSTRAINT "PK_ID" PRIMARY KEY ("ID")
)

Ich habe die folgende Sequenz erstellt und ausgelöst, um eine eindeutige Identität für jede Zeile zu erstellen:

CREATE SEQUENCE STVM_NOTIF_SEQ
  START WITH 1
  INCREMENT BY 1
  CACHE 100;

CREATE OR REPLACE TRIGGER STVM_NOTIF_ID_TRG BEFORE INSERT ON STVM_NOTIFICATION
  FOR EACH ROW
    BEGIN
      :NEW.ID := STVM_NOTIF_SEQ.NEXTVAL;
    END;

Ich stelle dann die folgenden Stipendien für STVM ein:

GRANT CREATE SESSION TO STVM;
GRANT CREATE TABLE TO STVM;
GRANT CREATE VIEW TO STVM;
GRANT CREATE ANY TRIGGER TO STVM;
GRANT CREATE ANY PROCEDURE TO STVM;
GRANT CREATE SEQUENCE TO STVM;
GRANT CREATE SYNONYM TO STVM;
GRANT CHANGE NOTIFICATION TO STVM;

Insert in die Tabelle funktioniert gut. Im Folgenden finden Sie die einfache Test-App, die in der Oracle-Dokumentation zu OracleDependency (mit geringfügigen Änderungen) bereitgestellt wird, mit der ich Benachrichtigungen teste:

namespace SqlDependencyTest
{
    class Program
    {
        private static string oraConnectionString = @"Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.0.164)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XE)));User Id=STVM;Password=STVM;";
        private static string oraQuery = "SELECT ID FROM STVM_NOTIFICATION";
        private static OracleDependency oraDependency;

        static void Main(string[] args)
        {
            using (OracleConnection oraConnection = new OracleConnection(oraConnectionString))
            {
                try
                {
                    // Open the connection
                    oraConnection.Open();

                    // Create the Select command retrieving all data from the STVM_NOTIFICATION table. 
                    OracleCommand selectCommand = new OracleCommand(oraQuery, oraConnection);
                    // Create an OracleDependency object and set it to track the result set returned by selectCommand. 
                    oraDependency = new OracleDependency(selectCommand);

                    // Setting object-based change notification registration 
                    oraDependency.QueryBasedNotification = false;

                    // When the IsNotifiedOnce property is true, only the first change  
                    // of the traced result set will generate a notification. 
                    // Otherwise, notifications will be sent on each change  
                    // during the selectCommand.Notification.Timeout period. 
                    selectCommand.Notification.IsNotifiedOnce = true;

                    // Set the event handler to the OnChange event. 
                    oraDependency.OnChange += new OnChangeEventHandler(OnChange);

                    // When the select command is executed at the first time, a notification  
                    // on changes of the corresponding result set is registered on the server.
                    //selectCommand.CommandTimeout = 5;
                    OracleDataReader reader = selectCommand.ExecuteReader(CommandBehavior.Default);

                    // Set and execute an insert command. The Dept table data will be changed,  
                    // and a notification will be sent, causing the OnChange event of the 'dependency' object. 
                    OracleCommand insertCommand = new OracleCommand
                        ("INSERT INTO STVM_NOTIFICATION (PROPERTYNAME, PROPERTYVALUE, ACTION, POSTDATE, SENT) VALUES ('Heartbeat', 'NOK', 'REFRESH', SYSDATE, 'N')", oraConnection);
                    insertCommand.ExecuteNonQuery();

                    // Pause the current thread to process the event. 
                    Console.Read();
                }
                catch (Exception e)
                {
                    Console.WriteLine("Exception encountered: {0}", e.Message);
                }
                // Always try to both remove the notification registration
                // oraConnection.Close() is autimatically called by .Dispose at the end of our 'using' statement
                finally
                {
                    try
                    {
                        oraDependency.RemoveRegistration(oraConnection);
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Exception encountered: {0}", e.Message);
                    }
                }
            }
        }

        // A simple event handler to handle the OnChange event. 
        // Prints the change notification details. 
        private static void OnChange(Object sender, OracleNotificationEventArgs args)
        {
            DataTable dt = args.Details;

            Console.WriteLine("The following database objects were changed:");
            foreach (string resource in args.ResourceNames)
            {
                Console.WriteLine(resource);
            }

            Console.WriteLine("\n Details:");
            Console.Write(new string('*', 80));
            for (int rows = 0; rows < dt.Rows.Count; rows++)
            {
                Console.WriteLine("Resource name: " + dt.Rows[rows].ItemArray[0]);
                string type = Enum.GetName(typeof(OracleNotificationInfo), dt.Rows[rows].ItemArray[1]);
                Console.WriteLine("Change type: " + type);
                Console.Write(new string('*', 80));
            }
        } 
    }
}

Das funktioniert tatsächlich! Jedoch:nur, bis ein anderer Prozess eine Einfügung für dieselbe Tabelle durchführt, zumindest ist das meine Beobachtung. Ich habe die Einfügung mehrmals in SQL Developer ausgeführt. Das Problem ist nachvollziehbar, und das habe ich bereits über 10 Mal getan.

Sobald ein anderer Prozess diese Einfügung ausführt, hängt meine Anwendung an der folgenden Anweisung:OracleDataReader reader = selectCommand.ExecuteReader(CommandBehavior.Default);

Ich kann deutlich sehen, dass der Benachrichtigungsrückruf in @ registriert is DBA_CHANGE_NOTIFICATION_REGS: net8://(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.0.226)(PORT=64268))?PR=0

Die Verbindung bleibt auf dem Oracle-Server 30 Minuten lang geöffnet, bis das Zeitlimit überschritten wird:

SELECT USERNAME, PROGRAM, BLOCKING_SESSION_STATUS, BLOCKING_INSTANCE, BLOCKING_SESSION, EVENT FROM V$SESSION
WHERE USERNAME = 'STVM'

USERNAME    PROGRAM                         BLOCKING_SESSION_STATUS BLOCKING_INSTANCE   BLOCKING_SESSION    EVENT
STVM        SQL Developer                   NO HOLDER               (null)              (null)              SQL*Net message from client
STVM        OracleDependencyTest.vshost.exe VALID                   1                   50                  enq: TM - contention

Wenn dies geschieht, besteht die einzige Lösung darin, die Sitzungen abzubrechen, die Änderungsbenachrichtigung zu widerrufen und sie erneut zu gewähren. Danach funktioniert meine Anwendung wieder, bis ein anderer Prozess eine Einfügung ausführt.

Der einzige Hinweis, den ich habe, ist das Ereignisenq: TM - contention, aber die meisten Leute bezeichnen dies als Indikator für nicht indizierte Fremdschlüssel in der Tabelle. Angesichts der Tatsache, dass ich derzeit nur eine einzige Tabelle zu Testzwecken habe, gibt es hier nicht viele Fremdschlüssel.

Hat jemand irgendwelche Ideen bezüglich der Relevanz vonenq: TM - contention?

Ich benutze

(Serverseite)

Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production
PL/SQL Release 11.2.0.2.0 - Production
"CORE   11.2.0.2.0  Production"
TNS for Linux: Version 11.2.0.2.0 - Production
NLSRTL Version 11.2.0.2.0 - Production
Oracle.DataAccess

(Client-Seite)

Oracle Call Interface (OCI)                                          11.2.0.1.0
Oracle Client                                                        11.2.0.1.0
Oracle Data Provider for .NET                                        11.2.0.1.0
Oracle JDBC/OCI Instant Client                                       11.2.0.1.0
Oracle JDBC/THIN Interfaces                                          11.2.0.1.0
Oracle SQL Developer                                                 11.2.0.1.0
SQL*Plus                                                             11.2.0.1.0
SQL*Plus Files for Instant Client                                    11.2.0.1.0

AKTUALISIERE: Nachdem ich tagelang versucht hatte, herauszufinden, wo das Problem liegt, und das Problem nicht gefunden hatte, habe ich mich entschlossen, nach einer anderen Technik zu suchen. Der großartige Vorschlag von Christian brachte keine Ergebnisse, und die Lösung der unbestätigten Transaktion, wie sie Justin vorgeschlagen hatte, brachte mich leider auch nicht weiter. Ich kenne ein paar Oracle-Datenbankadministratoren, die anfänglich bereit waren zu helfen, aber sie haben mich schnell ausgeschlossen, sobald ich .NET erwähnt habe.

Antworten auf die Frage(1)

Ihre Antwort auf die Frage