Blog Schröder

Sammlung von Codeschnipseln zu Programmierproblemen.
Daten aus fremden Quellen unterliegen deren Rechten.
Siehe auch: Disclaimer auf www.computer-schroeder.de

Mittwoch, 26. September 2007

Datum und Kulturen

dattime.ToString("dd.mm.yyyy HH:mm", CultureInfo.InvariantCulture)
(mehr: http://groups.google.com/group/ microsoft.public.de.german.entwickler.dotnet.asp/ browse_thread/thread/0102785d04fa218a#)
(Leerzeichen entfernen!)

Labels: , ,

Montag, 24. September 2007

GUID in Access erzeugen

Option Explicit

Public Type TYP_GUID
bytes(15) As Byte
End Type

Public Declare Function CoCreateGuid Lib "OLE32.dll" _
(Guid As TYP_GUID) As Long
Public Declare Function StringFromGUID2 Lib "OLE32.dll" _
(Guid As TYP_GUID, _
ByVal lpszString As String, _
ByVal iMax As Long) As Long

Public Function CreateGUID() As String
'// --------------------------------------------------------
'// Methode: | Erzeugen einer eindeutigen GUID
'// --------------------------------------------------------
'// Parameter: | -
'// --------------------------------------------------------
'// Rückgabe: | GUID als String
'// | z.B.:{A7FFA6B1-0377-4A75-8CB9-B95FB5CF545A}
'// --------------------------------------------------------
'// Autor: | Stefan Kulpa
'// | EDV Innovation & Consulting - Dormagen
'// --------------------------------------------------------
Dim uGuid As TYP_GUID
Dim sBuffer As String
Dim lResult As Long

sBuffer = VBA.Space(78)
CoCreateGuid uGuid
lResult = StringFromGUID2(uGuid, sBuffer, Len(sBuffer))
CreateGUID = _
Left$(StrConv(sBuffer, vbFromUnicode), lResult - 1)

End Function
(www.kulpa-online.com)

Labels: ,

Alle Frontends einer Multiuser-DB schließen

Zur Wartung oder zur Datensicherung ist es erforderlich, alle geöffneten Frontends einer Datenbank zu schließen. Oft machen einem die User zusätzliche Arbeit, indem sie nach Feierabend die Datenbank geöffnet lassen.

Dafür habe ich folgende Lösung entworfen. In der Datenbank (Backend) muss eine zusätzliche Tablle (tblShutdown) angelegt werden, die nur das Ja/Nein-Feld shutdown enthält. Diese Tabelle kann man dann in ein Administrationsfrontend einbinden und von dort über das Setzen des Ja/Nein Wertes in dem einzigen Datensatz das globale Schließen der Frontends einleiten.

Im Frontend muss ein Timer laufen (z.B. in einem unsichtbaren Formular), der in gewissen Abständen die Sub closeDB ausführt.

Probleme kann es geben, wenn in einem der Frontends der aktuelle Datensatz eine unvollständige Eingabe ist, die nicht gespeichert werden kann. Diese Situation sollte man unbedingt vor Einsatz der Funktion bedenken.

Sub closeDB()

Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset _
("SELECT shutdown FROM tblShutdown")

If rs!shutdown = True Then
rs.Close
Application.CloseCurrentDatabase
End If

rs.Close

End Sub
(www.codekabinett.com)

Labels: , ,

Verbindung eines ADP zur Laufzeit festlegen

Eigentlich sollte man annehmen, dass man zu diesem Zweck einfach die Connection.Open-Methode der Currentproject.Connection (ADODB.Connection) verwenden kann. Dies trifft aber nur indirekt zu. Es funktioniert nicht, das Connection-Objekt direkt zu verwenden. Stattdessen muss man die OpenConnection- bzw. CloseConnection-Methode des CurrentProject-Objektes verwenden.

Den richtigen ConnectionString für die Verbindung zu ermitteln kann evtl. ein weiteres Problem darstellen. Denn wenn man sich den Connectionstring der CurrentProject.Connection anschaut, wird dort als Provider "Microsoft.Access.OLEDB.10.0" verwendet. Wenn man aber versucht, mit diesem Provider eine Verbindung zu öffnen, schlägt dies immer fehl. Stattdessen muss man auch für Access einen typischen OleDB-ConnectionString zum SQL-Server verwenden und dabei den Provider "SQLOLEDB.1" angeben, so wie man ihn aus der BaseConnection-Property des CurrentProject-Objektes auslesen kann. Es funktioniert übrigens nicht, dort einen Provider für ein anderes DBMS anzugeben.

Mit den obigen Informationen ausgestattet ist es eine Kleinigkeit, eine VBA-Prozedur zu schreiben, die dem ADP eine neue Verbindung zuweist.

Public Sub OpenADPConnection(ByVal strUser As String, ByVal strPassword As String)

Const strCONNECTION_STRING As String = _
"Provider=SQLOLEDB;Data Source=deinServer;Initial Catalog=deineDatenbank;"

CurrentProject.OpenConnection strCONNECTION_STRING, strUser, strPassword

If Not CurrentProject.IsConnected Then
MsgBox "Es konnte keine Verbindung hergestellt werden!"
End If

End Sub

Zuletzt noch ein Hinweis zu einem eng verwandten Problem. Meist möchte man ja die Verbindung eines ADP zur Laufzeit setzen, um eine fertige Anwendung in der Umgebung des Kunden zu deployen. Dabei tritt der unangenehme Effekt auf, dass Access beim Öffnen eines ADP erst mal versucht, die Verbindung zu dem Server und der Datenbank herzustellen, die zuletzt verwendet wurden. Da der Entwicklungsserver i.d.R. bei dem Wechsel von einer Entwicklungsumgebung zu dem Live-System aber nicht mehr direkt zur Verfügung steht, bleibt die Access-Anwendung dann beim Start so lange hängen, bis der Connection-Timeout abgelaufen ist und anschließend wird eine Fehlermeldung ausgegeben, dass der Server nicht erreichbar ist.

Um diesen unangenehmen Effekt zu verhindern, sollte man seine ADP-Anwendung ohne Verbindungsdaten ausliefern und dann, wie oben beschrieben, die Verbindung per VBA beim Start der Anwendung aufbauen. Die gespeicherten Verbindungsinformationen kann man aus der Anwendung entfernen, indem man per VBA die OpenConnection-Methode ohne Parameter aufruft.

Call CurrentProject.OpenConnection

Danach ist das ADP beim Start verbindungslos und es wird nicht mehr automatisch versucht, eine Verbindung aufzubauen.

(www.codekabinett.com)

Labels: , , ,

CurrentUser in Access Data Project (ADP)

In einer Access-MDB-Anwendung war es mit der Funktion CurrentUser() möglich, den angemeldeten Benutzer der Anwendung zu ermitteln. Der Aufruf von CurrentUser liefert in einem ADP aber immer "Admin" zurück, egal wer gerade angemeldet ist.

Die Erklärung dafür ist einfach. In einem ADP wird die Benutzerverwaltung von dem SQL-Server-Backend übernommen. Es gibt in Access tatsächlich nur noch den einen Benutzer "Admin". Wenn man nun wissen möchte, welcher Benutzer am SQL-Server angemeldet ist, muss man dazu auch den SQL-Server befragen.

Die T-SQL-Funktion SUSER_SNAME() liefert den Login des Benutzers am SQL-Server. Dieser Login kann sich durchaus vom Benutzernamen in der aktuellen Datenbank unterscheiden. Dieser Funktion kann die interne ID eines Logins auf dem SQL-Server als Parameter übergeben werden, um den UserName eines anderen Benutzers zu ermittelt. Für unseren Zweck reicht es aber aus, die SUSER_SNAME-Funnktion ohne Parameter aufzurufen. Also sieht das komplette SQL-Statement wie folgt aus:

SELECT SUSER_SNAME()

Wenn man nicht den Login-Namen auf den Server, sondern den Benutzernamen in der aktuellen SQL-Server-Datenbank benötigt, kann man analog zu dem obigen Beispiel die Funktion USER bzw. USER_NAME() verwenden.

Um aus Access heraus diese Information zu erhalten, muss man ein Recordset mit diesem SQL-Statement öffnen und den Wert dieses Feldes auslesen. Diesen Vorgang kann man komfortabel in eine eigene VBA-Funktion kapseln, die dann in dem Projekt die eingebaute Funktion CurrentUser ersetzt.

Hier ein Beispiel für eine solche Funktion:

Public Function AktuellerBenutzer() As String
On Error GoTo AktuellerBenutzer_Err

Dim rs As ADODB.Recordset

Const strSQL As String = "SELECT SUser_SName()"

Set rs = CurrentProject.Connection.Execute(strSQL)

AktuellerBenutzer = rs.Fields(0).Value

AktuellerBenutzer_Exit:
On Error Resume Next
rs.Close
Set rs = Nothing
Exit Function

AktuellerBenutzer_Err:
MsgBox Err.Number & " " & Err.Description, vbExclamation, "Error"
Resume AktuellerBenutzer_Exit
End Function


Die hier erwähnten SQL-Server-Funktionen enstammen dem Funktionsumfang des SQL-Servers 8.0 (SQL 2000).
(www.codekabinett.com)

Labels: , , ,

Datenbank 'xyz' wird schreibgeschützt geöffnet... bei ADP-Mehrbenutzerbetrieb

Wenn mehrere Benutzer die ADP-Datei öffnen, bekommen alle Benutzer nach dem ersten die Meldung 'Die Datenbank 'xyz' wird schreibgeschützt geöffnet...' Was kann man dagegen tun?

Das ist so 'by Design'. Bei einer MDB-Anwendung werden die Informationen, welcher Benutzer welche Objekte bearbeitet und damit sperrt, in der LDB-Datei gespeichert. Dadurch ist es möglich, dass mehrere Benutzer gleichzeitig Schreibzugriff auf ein und dieselbe Datei haben.

Bei einem ADP gibt es keine LDB-Datei, daher muss Access immer davon ausgehen, dass der erste Benutzer, der die Datei öffnet Änderungen an den Objektdefinitionen vornimmt und sperrt die gesamte Datei. Dadurch haben alle weiteren Benutzer nur noch schreibgeschützen Zugriff auf die Datei. Der Schreibschutz gilt nur für die ADP-Datei, d.h. Formulare, Berichte, Module, etc. aber nicht für die Daten auf dem SQL-Server.

Als einfachen Workaround für dieses Problem kann man Access im Runtime-Modus starten und die ADP-Datei öffnen. Im Runtime-Modus kann man sowieso keine Änderungen an den Objektdefinitionen vornehmen, daher unterbleibt in diesem Fall der Hinweis auf dem Schreibschutz.

Diesen Workaround kann man einfach realisieren, indem man die Anwendung über eine Verknüpfung startet und dort den Runtime-Switch in der Befehlszeile angibt. Die Befehlszeile einer Verknüpfung sieht dann etwa wie folgt aus:

"C:\Pfad\zu\MSACCESS.EXE" /runtime "C:\Pfad\zur\Anwendung.adp"

Dieser Workaround funktioniert aber nur mit maximal 20 geöffneten Instanzen der Anwendung. Wenn die Anwendung 21 mal oder mehr geöffnet wird, erscheint die Fehlermeldung 'Die ADP-Datei ist nicht im richtigen Microsoft Access Projektformat.' und es ist nicht mehr möglich weitere Instanzen zu öffnen.

Eine bessere Lösung für das Problem ist es, jedem Benutzer eine eigene Kopie der Datei auf seinem lokalen Rechner zur Verfügung zu stellen. Zu einem bringt dieses Vorgehen leichte Performancevorteile, da die Access-Objekte nicht über das Netzwerk geladen werden müssen. Außerdem ist dieser Ansatz stabiler, da jeder Benutzer nur in seiner eigenen Datei arbeitet und wenn diese beschädigt wird, keine anderen Benutzer beeinträchtig werden.

Um eine Anwendung in einem solchen Szenario regelmäßig updaten zu können, kann auf jedem Client der Start der Anwendung über ein Script oder ein Zusatzprogramm erfolgen, das ggfls. eine aktualisierte Version von einem Netzlaufwerk auf den lokalen Rechner kopiert. Diese Funktionalität kann man entweder selbst entwicklen, oder auf fertige Lösungen wie z.B. den Auto FE Updater von Tony Toews verwenden.
(www.codekabinett.com)

Labels: , , ,

Dienstag, 18. September 2007

Row limit: Nur 10.000 Datensätze

Du hast in einem Access-ADP das Problem, dass du eine große Menge an Datensätzen anzeigen oder verarbeiten (UPDATE/INSERT/DELETE) willst, aber dass immer nur maximal 10.000 Datensätze angezeigt bzw. verarbeitet werden.

Der einfache Grund hierfür ist, dass Access per Standardeinstellung die Anzahl der bearbeiteten Datensätze auf 10.000 begrenzt.

Das Problem lässt sich mit zwei verschiedenen Ansätzen lösen. Die einfache Lösung ist, die Standardeinstellung von Access den eigenen Bedürfnissen anzupassen. Dazu kann man über das Menü Extras-Optionen-Weitere-Client/Server-Einstellungen-Vorgabe der max. Datensätze einfach einen anderen geeigneten Wert einstellen. Diese Vorgabe gilt jeweils nur für die aktuelle Datenbank.

Diese Änderung der Standardeinstellung lässt sich auch über VBA-Code vornehmen, indem man über die SetOption-Methode die Row Limit-Option verändert. Der folgende Beispielcode demonstriert dies (das Row Limit wird auf 500 Datensätze reduziert):

Call Application.SetOption("Row Limit",500)

Bevor man das Thema damit komplett abhakt, sollte man aber bedenken, dass das Access-Team von Mircosoft diese Voreinstellung nicht einfach nur vorgenommen hat, um uns arme Entwickler zu ärgern, sondern dass diese Voreinstellung durchaus ihre Berechtigung hat. Überleg mal; wer will ersthaft eine Liste mit mehr als 10.000 Datensätzen am Bildschirm durchgehen, oder sich gar duch soviele Datensätze in einer Einzelansicht klicken? - Normalerweise niemand! Weiterhin sollte man bedenken, dass mit einer globalen Änderung dieser Option der SQL-Server dazu gezwungen wird eine Menge Datensätze zum Access-Client zu schaufeln, für die sich dort niemand interessiert. Das belastet den Server und das Netzwerk völlig unnötig.

Anstatt das Row Limit für alle Abfragen in den Access-Standardeinstellungen zu verändern kann man auch in alle Stored Procedures auf dem SQL-Server, die Änderungen an dem Datenbestand vornehmen den Rowcount (Synonym für die Row Limit-Option in Access) per T-SQL setzen. Die entsprechende T-SQL-Anweisung lautet einfach:

SET ROWCOUNT 0

Der Wert 0 für den Rowcount deaktiviert die Beschränkung der betroffenen Datensätze vollständig. D.h. es werden immer alle Datensätze verarbeitet, die den jeweiligen Kriterien entsprechen. Wenn man den Rowcount innerhalb einer Stored Procedure setzt, gilt der jeweilige Wert nur innerhalb der Stored Procedure, nicht für die gesamte Verbindung.

Natürlich kann man auch vor AdHoc-SQL-Statements, die man über die Connection des ADPs ausführt, den Rowcount, wie oben beschrieben, per T-SQL auf einen geeigneten Wert setzen. Allerdings möchte ich dieses Vorgehen nicht empfehlen, da man das dann vor jedem einzelnen SQL-Statement berücksichtigen muss und das meiner Meinung nach zu fehlerträchtig ist. Wenn man also von Ad-Hoc-SQL gebrauch macht um Daten zu ändern, sollte man besser dafür sorgen, dass das Row Limit für die ganze Verbindung des ADPs immer korrekt eingestellt ist. Die bessere Alternative ist natürlich grundsätzlich sämtliche DML-Aktionen ausschließlich über Stored Procedures auszuführen.

Ich würde in jedem Fall für alle Stored Procedures, die Daten verändern immer explizit den ROWCOUNT innerhalb der Stored Procedure auf 0 setzen, wie oben beschrieben. Damit ist sichergestellt, dass die Stored Procedure als autarke Einheit immer funktioniert und nur von eventuellen Eingabeparametern, aber nicht von gobalen Einstellungen, wie dem Rowcount der Connection, abhängig ist.

Realisierung:
Private Sub Form_Open(Cancel As Integer)
MaxDatensätze = Application.GetOption("Row Limit")
Application.SetOption "Row Limit", 0
End Sub

Private Sub Form_Close()
Application.SetOption "Row Limit", MaxDatensätze
End Sub

Quelle: www.codekabinett.com

Labels: ,

Freitag, 7. September 2007

verlorenen Benutzer ASPNET wiederherstellen

Zuerst: aspnet_regiis /u (Deinstallation ASP.NET)
Dann: aspnet_regiis /i (Installation ASP.NET)
Protokolle dazu heißen ASPNETSetup_0000i.log und liegen in
C:\Dokumente und Einstellungen\Username\Lokale Einstellungen\Temp

Labels: , , ,