Benutzer mit den meisten Antworten
Aufgaben an VB.Net abgeben oder Parallel ausführen

Frage
-
Ich habe es immer mal wieder das ich in Access Aufgaben habe die nicht Ausführungs - Zeitrelevant sind aber meine Access Anwendung abbremsen.
zb mal eine Abfrage die LogTabellen bis auf X Datensätze löscht
oder Dateien die angelegt werden oder Kopiert werden…
alles Dinge von denen ich keine Rückgabe erwarte/benötige.
Nun möchte ich diese Dinge „abgeben“ oder „Parallel“ laufen lassen und suche dafür einen Ansatz
Kann ich mit VB.net eine Programm schreiben und dieses in Access mit einbinden?
Ähnlich einer DLL
Oder kann ich Access dazu bringen Parallel arbeite auszuführen?
Mir geht es darum an einigen Punkten in der Anwendung „zeit“ zu sparen die zwar nicht lang ist aber die Anwendung sich dadurch nicht flüssig anfühlen lässt
Antworten
-
Hi,
eigentlich ist die Realisierung recht einfach.1. Du schreibst in einer .NET-Sprache (z.B. VB.NET) ein Klassenbibliotheks-Projekt.
2. Die in der COM-Welt sichtbare Klasse versiehst Du mindestens mit einer GUID und dem ComVisible-Attribut.
3. Das Bibliotheks-Projekt kennzeichnest Du mit "Make assembly COM-Visible".
4. Zielplattform ist als X86 zu setzen.
5. Die erzeugte dll ist dann im System zu registrieren (automatisch oder mit RegAsm.exe -tlb -codebase mydll.dll)
Viel wichtiger ist die Programmlogik. Die aufgerufene Methode der .NET-dll wird synchron abgearbeitet. Wenn Du dort eine asynchrone Arbeit starten willst, dann ist wichtig festzulegen, wie das Ende der asynchronen Arbeit auf das Hauptprogramm wirken soll.
Da die Übergabe von Objekten aus Access zur Nutzung in der .NET-dll nur Probleme bringen kann, sollte eine Art von Aufgaben in eine "Warteschlange" in der Access-Anwendung eingereiht werden. Die Warteschlange könnte z.B. eine temporäre Access-Tabelle sein. Mit einem Aufruf einer Methode in der .NET-dll wird dann ein asynchroner Prozess zur Verarbeitung eines Warteschlangeneintrages gestartet. In der .NET-dll wird ohne Rücksicht auf die Access-Anwendung ein eigener Zugriff auf die Access-Datenbank organisiert. Das bedeutet, dass zwei konkurrierende Anwendungen auf die Datenbank zugreifen, was in der Logik zu berücksichtigen ist. Dieser Ablauf entspricht logisch in etwa dem Beitrag von Markus.
Hier mal ein Gerüst für eine Lösung. Es wird in VB.NET eine Start-Methode aufgerufen, der im Beispiel ein Parameter übergeben wird, dann ein Thread gestartet und über ein Ereignis wird das Ende gemeldet.
Imports System.Runtime.InteropServices <ComVisible(False)> Public Delegate Sub SomeEventHandler(response As String) <Guid("032F0C1E-3AD1-4BF7-A4BC-37769EC4560D"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> Public Interface ComClass1Interface <DispId(1)> Sub Start(msg As String) End Interface <Guid("55139E66-AD7D-4387-9E39-9117FBB34009"), ComVisible(True), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> Public Interface IComClass1Events <DispId(2)> Sub SomeEvent(response As String) End Interface <Guid("53CC5ACE-712F-4BDA-9B72-503C42F99F99"), ComVisible(True), ClassInterface(ClassInterfaceType.None), ComSourceInterfaces(GetType(IComClass1Events))> Public Class ComClass1 Implements ComClass1Interface Public Event SomeEvent As SomeEventHandler Public Sub Start(msg As String) Implements ComClass1Interface.Start Dim c As New ThreadClass With {.Message = msg} AddHandler c.ThreadEnde, AddressOf StartEnde Dim th As New Threading.Thread(New Threading.ThreadStart(AddressOf c.Execute)) th.Start() End Sub Private Sub StartEnde(response As String) RaiseEvent SomeEvent(response) End Sub End Class Friend Class ThreadClass Friend Event ThreadEnde As SomeEventHandler Friend Property Message As String Friend Sub Execute() Threading.Thread.Sleep(5000) ' Verzögerung der Arbeit simulieren RaiseEvent ThreadEnde($"{Date.Now.ToLongTimeString} {Message}") End Sub End Class
Der Prinzipaufruf in VB6 (z.B. im Access-VBA) sieht dann so aus:
Public WithEvents obj As ComClass1 Sub Execute() Set obj = New ComClass1 obj.Start "Los geht's " & Now End Sub Private Sub obj_SomeEvent(ByVal response As String) MsgBox response End Sub
--
Viele Grüsse
Peter Fleischer (ehem. MVP)
Meine Homepage mit Tipps und Tricks
- Bearbeitet Peter Fleischer Samstag, 4. August 2018 08:43 Code hinzugefügt, korrigiert
- Als Antwort vorgeschlagen Peter Fleischer Samstag, 4. August 2018 08:44
- Als Antwort markiert MCDPone Donnerstag, 9. August 2018 05:39
Alle Antworten
-
Hallo!
Das klingt eh nach einer langfristigen Sache. Falls du hier keine zielführenden Antworten/Beispiele erhalten solltest und noch 2,5 Monate Zeit hast: Auf der AEK im Oktober wird es genau zu diesem Thema einen Vortrag geben (Access/VBA –> Interop –> .NET).
Also entweder anmelden, um dich richtig auszukennen und den Referenten persönlich zu löchern, oder nach dem zweiten Termin schauen, ob die freien Downloads ausreichen, um die Techniken nachzuvollziehen.
(Ende der kleinen Werbeeinschaltung. ;-)
- Bearbeitet Karl DonaubauerMVP Dienstag, 31. Juli 2018 14:17 Sig
-
Ich hab mich vor kurzem mit diesem Thema gespielt.
Im Ergebnis verwende ich eine zweite unsichtbar Access Anwendung, die Aufgaben parallel abarbeitet, oder ein kleines Script Programm das echtes Multithreading (QuickMacros) ermöglicht.
Im meinem Fall ging es zuerst darum, dass ein User einen länger dauernden Prozess unterbrechen kann.
Ich erreiche so, dass das Formular sofort auf seine Eingaben reagiert - ansonsten müsste er warten, bis ein DoEvents aus dem Hauptprozess veranlasst wird. So bekommt er sofort ein Feedback.
Vom Konzept her:
Es wird per Run ein Sub in der zweiten Anwendung gestartet.
Die zweite Anwendung startet einen Timer, damit die Steuerung wieder sofort an die rufende Anwendung zurückkehrt.
Unmittelbar danach beginnt die parallele Abarbeitung in der zweiten Anwendung.
Gruss
Markus
- Bearbeitet markus888 Dienstag, 31. Juli 2018 14:51
-
Danke erst einmal für deine Antwort
Zweite Access Anwendung mache ich auch - finde es aber nicht so wirklich schön - weil so wirklich unsichtbar bekomme ich die nicht
was meinst du mit script? was nutzt du dafür visual studio?
- Bearbeitet MCDPone Donnerstag, 2. August 2018 10:02 s
-
Zweite Access Anwendung mache ich auch - finde es aber nicht so wirklich schön - weil so wirklich unsichtbar bekomme ich die nicht
Du musst die Anwendung entweder unsichtbar starten (ich start mit der Shell Funktion), oder nach dem Start das Hauptfenster per API unsichtbar setzen.
Scriptprogramm: Quick Macros. Mit Visual Studio hab ich bis jetzt nicht viel Erfahrung gemacht - da fehlt leider etwas die Zeit.
Markus
-
Hi,
eigentlich ist die Realisierung recht einfach.1. Du schreibst in einer .NET-Sprache (z.B. VB.NET) ein Klassenbibliotheks-Projekt.
2. Die in der COM-Welt sichtbare Klasse versiehst Du mindestens mit einer GUID und dem ComVisible-Attribut.
3. Das Bibliotheks-Projekt kennzeichnest Du mit "Make assembly COM-Visible".
4. Zielplattform ist als X86 zu setzen.
5. Die erzeugte dll ist dann im System zu registrieren (automatisch oder mit RegAsm.exe -tlb -codebase mydll.dll)
Viel wichtiger ist die Programmlogik. Die aufgerufene Methode der .NET-dll wird synchron abgearbeitet. Wenn Du dort eine asynchrone Arbeit starten willst, dann ist wichtig festzulegen, wie das Ende der asynchronen Arbeit auf das Hauptprogramm wirken soll.
Da die Übergabe von Objekten aus Access zur Nutzung in der .NET-dll nur Probleme bringen kann, sollte eine Art von Aufgaben in eine "Warteschlange" in der Access-Anwendung eingereiht werden. Die Warteschlange könnte z.B. eine temporäre Access-Tabelle sein. Mit einem Aufruf einer Methode in der .NET-dll wird dann ein asynchroner Prozess zur Verarbeitung eines Warteschlangeneintrages gestartet. In der .NET-dll wird ohne Rücksicht auf die Access-Anwendung ein eigener Zugriff auf die Access-Datenbank organisiert. Das bedeutet, dass zwei konkurrierende Anwendungen auf die Datenbank zugreifen, was in der Logik zu berücksichtigen ist. Dieser Ablauf entspricht logisch in etwa dem Beitrag von Markus.
Hier mal ein Gerüst für eine Lösung. Es wird in VB.NET eine Start-Methode aufgerufen, der im Beispiel ein Parameter übergeben wird, dann ein Thread gestartet und über ein Ereignis wird das Ende gemeldet.
Imports System.Runtime.InteropServices <ComVisible(False)> Public Delegate Sub SomeEventHandler(response As String) <Guid("032F0C1E-3AD1-4BF7-A4BC-37769EC4560D"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> Public Interface ComClass1Interface <DispId(1)> Sub Start(msg As String) End Interface <Guid("55139E66-AD7D-4387-9E39-9117FBB34009"), ComVisible(True), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> Public Interface IComClass1Events <DispId(2)> Sub SomeEvent(response As String) End Interface <Guid("53CC5ACE-712F-4BDA-9B72-503C42F99F99"), ComVisible(True), ClassInterface(ClassInterfaceType.None), ComSourceInterfaces(GetType(IComClass1Events))> Public Class ComClass1 Implements ComClass1Interface Public Event SomeEvent As SomeEventHandler Public Sub Start(msg As String) Implements ComClass1Interface.Start Dim c As New ThreadClass With {.Message = msg} AddHandler c.ThreadEnde, AddressOf StartEnde Dim th As New Threading.Thread(New Threading.ThreadStart(AddressOf c.Execute)) th.Start() End Sub Private Sub StartEnde(response As String) RaiseEvent SomeEvent(response) End Sub End Class Friend Class ThreadClass Friend Event ThreadEnde As SomeEventHandler Friend Property Message As String Friend Sub Execute() Threading.Thread.Sleep(5000) ' Verzögerung der Arbeit simulieren RaiseEvent ThreadEnde($"{Date.Now.ToLongTimeString} {Message}") End Sub End Class
Der Prinzipaufruf in VB6 (z.B. im Access-VBA) sieht dann so aus:
Public WithEvents obj As ComClass1 Sub Execute() Set obj = New ComClass1 obj.Start "Los geht's " & Now End Sub Private Sub obj_SomeEvent(ByVal response As String) MsgBox response End Sub
--
Viele Grüsse
Peter Fleischer (ehem. MVP)
Meine Homepage mit Tipps und Tricks
- Bearbeitet Peter Fleischer Samstag, 4. August 2018 08:43 Code hinzugefügt, korrigiert
- Als Antwort vorgeschlagen Peter Fleischer Samstag, 4. August 2018 08:44
- Als Antwort markiert MCDPone Donnerstag, 9. August 2018 05:39
-
Also noch einmal vielen dank für deine Antwort
ich bin weiter aber leider auch an meiner grenze
ich habe mir eine dll erstellt die ich auch aus Access heraus ansprechen kann
ich hänge das mal an
Imports System.Data.OleDb Imports System.Data.SqlClient <ComClass(CSR_Access.ClassId, CSR_Access.InterfaceId, CSR_Access.EventsId)> Public Class CSR_Access #Region "COM-GUIDs" Public Const ClassId As String = "88c9c1d9-da81-4f5f-919f-5b26b9594ca5" Public Const InterfaceId As String = "b5edffe4-ae93-4e5a-9eb9-50181a2a278c" Public Const EventsId As String = "8dae24ed-85ae-48f0-b454-fc9445b23b35" #End Region Public Function SQLAusfuehrenInMSSQL(ByVal WelcherConnectionString As String, ByVal WelcherSqlString As String, ByVal AbfrageKennung As String) As String Dim conn As SqlConnection = Nothing Try conn = New SqlConnection(WelcherConnectionString) Dim sqlCMD As New SqlCommand(WelcherSqlString, conn) conn.Open() Return sqlCMD.ExecuteNonQuery() Catch ex As Exception Return (ex.Message) Finally If Not IsNothing(conn) Then conn.Close() End Try End Function Public Function SQLAusfuehrenInACCDB(ByVal WelcherConnectionString As String, ByVal WelcherSqlString As String, ByVal AbfrageKennung As String) As String Dim conn As OleDbConnection = Nothing Try conn = New OleDbConnection(WelcherConnectionString) Dim sqlCMD As New OleDbCommand(WelcherSqlString, conn) conn.Open() Return sqlCMD.ExecuteNonQuery() 'Return Nothing Catch ex As Exception Return ex.Message Finally If Not IsNothing(conn) Then conn.Close() End Try End Function End Class
das funktioniert auch sehr gut ich kann schön aus Access die Functionen aufrufen und diese werden abgearbeitet
(leider muss ich hier eben so lange warten bis ich den Rückgabewert erhalte)
dann habe ich DEINE Variante mit den Threads erstellt Funktioniert Super nur verstehe ich nicht wie ich in der Version Übergaben von Access hin bekomme (wie bei mir mit ConnectionString oder wie ich Verschiedene Functionen dort anlegen kann
-
Hi,
kannst Du mal eine konkrete Aufgabenstellung für eine Demo formulieren. Dann könnte ich versuchen, eine Demo zu erstellen.Wenn Du von Access aus Methoden der .NET-dll aufrufst, kannst Du Parameter übergeben.
Das Ende der asynchronen Arbeit kann ein Ereignis auslösen, welches Ergebnisdaten zurückliefert. Die Ereignisroutine in Access kann dann die Daten zur weiteren Nutzung in Access aufbereiten, z.B. zur Anzeige.
--
Viele Grüsse
Peter Fleischer (ehem. MVP)
Meine Homepage mit Tipps und Tricks -
Hi
die Aufgabenstellung ist in "meinem" beispiel schon Abgebildet
in dem möchte ich zb. eine Funktion aufrufen in der ich ein SQL String an eine bestimmte Datenbank abschicke
ich habe in meier Demo 2 Funktionen
einmal
sql String an eine MSSQL datenbank und
SQL String an ACCDB über eine OLEDB Connection
den Connection String und den SQL String übergebe ich aus der Access Anwendung zusätzlich möchte ich eine Kennung übergeben aus der ich ersehen Kann aus welcher Access Funktion ich den Aufruf gemacht habe(damit ich den Rückgabe Wert oder Fehlermeldung in Access in einer Tabelle Schreiben kann)
Die DLL soll später wachsen weil ich einige nicht Zeitlich relevanten Dinge aus der Anwendung auslagern möchte damit diese sich an einigen stellen "flüssiger" anfühlt
zb temp Tabellen löschen bis auf X Datensätze
Dateien via ftp hoch/runter laden...
Eben Dinge die erledigt werden sollen aber ich in Acccess nicht warten möchte/brauch
-
Hi,
ich dachte, dass mein Beispiel eigentlich aussagekräftig ist. Hier mal eine Demo etwas erweitert:Im Access-Formular gibt es eine Befehlsschaltfläche "DataLoad" und den folgenden Hintergrundcode:
Option Compare Database Private WithEvents c As ComClass1 Private id As Integer Private Sub DataLoad_Click() If c Is Nothing Then Set c = New ComClass1 id = id + 1 c.ExecuteScalar id, CurrentDb.Name, "SELECT count(*) FROM Tab1" End Sub Private Sub c_SomeEvent(ByVal id As Long, ByVal response As String) MsgBox "Prozess: " & id & vbNewLine & response End Sub
Und die VB-NET-Klasse sieht dazu so aus:
' Das Bibliotheks-Projekt mit "Make assembly COM-Visible" kennzeichnen. ' Zielplattform als X86 setzen. Imports System.Data.OleDb Imports System.Runtime.InteropServices <ComVisible(False)> Public Delegate Sub SomeEventHandler(id As Integer, response As String) <Guid("5EBDEBEB-D4BD-4B16-B668-83E8E8C09D35"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> Public Interface ComClass1Interface <DispId(1)> Sub ExecuteScalar(id As Integer, dbName As String, sql As String) End Interface <Guid("0D9F42A5-7540-4FF3-9010-821126D88B47"), ComVisible(True), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> Public Interface IComClass1Events <DispId(2)> Sub SomeEvent(id As Integer, response As String) End Interface <Guid("03E0273C-5473-4B29-BE99-F90903334436"), ComVisible(True), ClassInterface(ClassInterfaceType.None), ComSourceInterfaces(GetType(IComClass1Events))> Public Class ComClass1 Implements ComClass1Interface Public Event SomeEvent As SomeEventHandler Public Sub ExecuteScalar(id As Integer, dbName As String, sql As String) Implements ComClass1Interface.ExecuteScalar Dim c As New ThreadClass With {.ID = id, .DbName = dbName, .Sql = sql} AddHandler c.ThreadEnde, AddressOf StartEnde Dim th As New Threading.Thread(New Threading.ThreadStart(AddressOf c.ExecuteScalar)) th.Start() End Sub Private Sub StartEnde(id As Integer, response As String) RaiseEvent SomeEvent(id, response) End Sub End Class Friend Class ThreadClass Friend Event ThreadEnde As SomeEventHandler Friend Property ID As Integer Friend Property DbName As String Friend Property Sql As String Friend Sub ExecuteScalar() Try Dim cnstring = $"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={DbName}" Using cn As New OleDbConnection(cnstring) Using cmd As New OleDbCommand(Sql, cn) cn.Open() Dim res = cmd.ExecuteScalar Threading.Thread.Sleep(1000) ' Verzögerung der Arbeit simulieren RaiseEvent ThreadEnde(ID, $"{Date.Now.ToLongTimeString} Result: {res}") End Using End Using Catch ex As Exception RaiseEvent ThreadEnde(ID, $"{Date.Now.ToLongTimeString} Error: {ex.Message}") End Try End Sub End Class
Im Beispiel wird über ExecuteScalar der erste gelesene Wert über ein Ereignis an Access zurückgeliefert. Im Beispiel wird aus Access eine SQL Anfrage übergeben, die im Beispiel nur die Zahl der Datensätze in Tab1 per SQL ermittelt. Es wird eine Verzögerung für mögliche länger andauernde Arbeiten simuliert. Jede neue Anfrage bekommt eine neue ID. Für eine produktive Anwendung sollte jedoch berücksichtigt werden, dass die Jet bei intensiver Parallelarbeit mit ständigen Öffnen und Schließen der Verbindung den Dienst versagt ("aufhängt"). Deshalb sollte in diesem Fall die Com-Klasse mit Start und Ende Verbindungsaufbau separat zu Beginn und zum Ende der Arbeit aufgebaut werden. Alle Zugriffe laufen dann über die gleiche geöffnete Verbindung, wie das von Microsoft bei der Nutzung von Access empfohlen wird. Das muss dann in der Nutzung der COM-dll in Access berücksichtigt werden, um auch den belegten Speicherplatz wieder freizugeben.--
Viele Grüsse
Peter Fleischer (ehem. MVP)
Meine Homepage mit Tipps und Tricks