DataSet unique key / DataGridView über BindingSource

  • VB.NET

Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von HaRoWagner.

    DataSet unique key / DataGridView über BindingSource

    Hallo,

    mein Problem ist, dass ich den Key nicht vorgeben kann, weil es sich um eine Betriebsnummer handelt, die ist numerisch, 5-stellig und nicht kleiner als 10000. Das ganze ist über eine Bindigsource an eine Tabelle mit einer VPDID geknüpft. Eine VPDID: ein bis viele Betriebe aber jeder Betrieb darf nur einmal eingetragen werden.
    Genau da liegt mein Problem.
    Im dgv kann man das Ereignis CellValidating leicht umgehen, in dem man einfach mehrmals hintereinander Zeilen zufügt. Ich habe schon AllowDBNull im DataSet gesetzt um nicht sofort auf die Nase zu fallen und auch ein passendes DefaultValue im DataSet gesetzt, trotzdem muss ich irgendwann ja mal den "Fehler" abfangen.

    Ich komme nicht dahinter, wie ich das anfangen kann.

    Es ist nicht so, dass mein Prgramm nicht funktioniert, es ist nur etwas anfällig gegen Fehlbedienung in diesem mir ziemlich neuen Bereich (ich konnte mit Datenbindungen nicht viel anfangen bisher, bin aber mit den Ergebnissen bisher gut zufrieden).

    Welches Ereignis muss ich abfragen und wie kann ich dann so eine Zeile im dgv löschen, bevor das gebundene DataSet etwas mitbekommt und sich flach legt.

    Danke schon mal.
    HaRo

    HaRoWagner schrieb:

    Im dgv kann man das Ereignis CellValidating leicht umgehen, in dem man einfach mehrmals hintereinander Zeilen zufügt.
    Das CellValidating kann man nicht "umgehen".

    Ich habe schon AllowDBNull im DataSet gesetzt
    Bei Schlüsselspalten eine denkbar schlechte Idee

    um nicht sofort auf die Nase zu fallen
    ähm - ganz allgemein gilt in der Programmierung: je früher man auf die Nase fällt, desto besser. (ich glaub, das gilt sogar fürs richtige Leben)

    irgendwann ja mal den "Fehler" abfangen.
    Von welchem Fehler eigentlich ist die Rede?

    Also was meinst du wirklich?

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Hast Du schon mal was von Primary Key gehört?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Für Datenmigrationen (Handelssysteme alt in ein oraclebasierendes Neu-System) benötige ich lokal (auf dem Arbeitsplatz einzelner Mitarbeiter) für jedes Projekt ein neues Datenset. Das dient der lokalen Bearbeitung dieser Stammdaten um die Daten für das neue System aufbereiten zu können.
    Dazu lasse ich den Anwender die VPDID des Partners auf der neuen Datenbank angeben, dann wird ein Verzeichnis gewählt in dem die Verzeichnisse und die Settings dazu angelegt werden. die Settings sind bereits durch die Datenbindung, die ich per Code erzeuge mit der VPDID als Haupttabelle und Schlüssel dann abgelegt. Der erste Betrieb ist ebenfalls eingetragen, in diesem Fall ist VPDID und Betriebsnummer identisch. Die Betriebsspezifischen Daten stehen in der Tabelle "Betrieb", hier ist die Betriebsnummer der Primary Key und verweist auf die VPDID.

    (Bild vom DataSet wieder entfernt, da sich dieser Beitrag nicht "absenden" läßt).

    Wenn jetzt im Programm Datensätze zugefügt werden (egal ob über das NavigationsElement oder über das dgv), dann greift .CellEndEdit nicht, weil gar keine Zelle editiert wird.
    (Bild mit den Form-elementen entfert, da sich dieser Beitrag immer noch nicht "absenden" läßt)

    NAch zweimaligem Datensätze zufügen bringt dann das:

    System.Data.ConstraintException wurde nicht behandelt.
    Message=Die Spalte 'Betriebsnummer' hat die Einschränkung, dass sie eindeutig sein muss. Der Wert '10000' ist bereits vorhanden.
    Source=System.Data
    StackTrace:
    bei System.Data.UniqueConstraint.CheckConstraint(DataRow row, DataRowAction action)
    bei System.Data.DataTable.RaiseRowChanging(DataRowChangeEventArgs args, DataRow eRow, DataRowAction eAction, Boolean fireEvent)
    bei System.Data.DataTable.SetNewRecordWorker(DataRow row, Int32 proposedRecord, DataRowAction action, Boolean isInMerge, Boolean suppressEnsurePropertyChanged, Int32 position, Boolean fireEvent, Exception& deferredException)
    bei System.Data.DataTable.InsertRow(DataRow row, Int64 proposedID, Int32 pos, Boolean fireEvent)
    bei System.Data.DataView.FinishAddNew(Boolean success)
    bei System.Data.DataRowView.EndEdit()
    bei System.Windows.Forms.CurrencyManager.EndCurrentEdit()
    bei System.Windows.Forms.BindingSource.EndEdit()
    bei System.Windows.Forms.BindingSource.AddNew()
    bei System.Windows.Forms.BindingNavigator.OnAddNew(Object sender, EventArgs e)
    bei System.Windows.Forms.ToolStripItem.RaiseEvent(Object key, EventArgs e)
    bei System.Windows.Forms.ToolStripButton.OnClick(EventArgs e)
    bei System.Windows.Forms.ToolStripItem.HandleClick(EventArgs e)
    bei System.Windows.Forms.ToolStripItem.HandleMouseUp(MouseEventArgs e)
    bei System.Windows.Forms.ToolStripItem.FireEventInteractive(EventArgs e, ToolStripItemEventType met)
    bei System.Windows.Forms.ToolStripItem.FireEvent(EventArgs e, ToolStripItemEventType met)
    bei System.Windows.Forms.ToolStrip.OnMouseUp(MouseEventArgs mea)
    bei System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
    bei System.Windows.Forms.Control.WndProc(Message& m)
    bei System.Windows.Forms.ScrollableControl.WndProc(Message& m)
    bei System.Windows.Forms.ToolStrip.WndProc(Message& m)
    bei System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
    bei System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
    bei System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
    bei System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
    bei System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
    bei System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
    bei System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
    bei Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.OnRun()
    bei Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.DoApplicationModel()
    bei Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.Run(String[] commandLine)
    bei CROSS_Migrationswerkzeug.My.MyApplication.Main(String[] Args) in 17d14f5c-a337-4978-8281-53493378c1071.vb:Zeile 81.
    bei System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
    bei System.AppDomain.nExecuteAssembly(RuntimeAssembly assembly, String[] args)
    bei System.Runtime.Hosting.ManifestRunner.Run(Boolean checkAptModel)
    bei System.Runtime.Hosting.ManifestRunner.ExecuteAsAssembly()
    bei System.Runtime.Hosting.ApplicationActivator.CreateInstance(ActivationContext activationContext, String[] activationCustomData)
    bei System.Runtime.Hosting.ApplicationActivator.CreateInstance(ActivationContext activationContext)
    bei System.Activator.CreateInstance(ActivationContext activationContext)
    bei Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssemblyDebugInZone()
    bei System.Threading.ThreadHelper.ThreadStart_Context(Object state)
    bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
    bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
    bei System.Threading.ThreadHelper.ThreadStart()

    Ich weiß wo ich ansetzen muss um das zu verhindern. Die Betriebsnummer kann ich auch nicht vorgeben, da sie mir oder dem Programm noch gar nicht bekannt ist. Ich kann nur den Schlüssel vordefinieren mit dem kleinsten möglichen Wert, nämlich "10000".

    HaRo

    HaRoWagner schrieb:

    Ich weiß wo ich ansetzen muss
    ähm - das CellValueChanged jedenfalls ist der falsche Ansatzpunkt.

    Dein Problem ist, deinen Datensätzen eindeutige Primkeys angedeihen zu lassen.
    Und da bin ich verwirrt, denn iwie scheinst du dir nicht nicht selbst welche generieren zu dürfen.
    Also woher willst du sie dann hernehmen, die eindeutigen Werte für die Primkeys?
    Weil was passiert, wenn du eine Dopplung hast, ist dir jetzt ja bekannt.
    Ahm, ja, da fehlte ein NICHT. Ich weiß nicht wo ich ansetzen muss. Leider.

    Du siehst das völlig korrekt. Die Angaben "weiß" der entsprechende Kollege, in dem die Daten vom Konzern für den diese Konvertierungen geschehen an den betreffenden MA geleitet werden.
    In der neuen Datenbank bekommt der Bereich für den Betrieb oder die Gruppe eine VPDID, die der Betriebsbummer des Haupt- oder Einzelbetriebs entspricht. Somit ist dem Programm bei Angabe der VPDID diese Betriebsnummer sofort bekannt (als Betriebsnummer). Welche (und ob überhaupt) weiter Betriebsnummern es für die VPDID geben wird ist erst einmal nicht bekannt. Die werden mit dem Unternehmen zusammen dann erfasst.
    Mit diesem Werkzeug möchte ich den Prozess der Datenmigration nur vereinfachen und eben auch flexibel handhaben können (ansonsten kann ich es eh gleich vergessen)
    Sicher ist nur: Jede VPDID gibt es genau einmal (hinterlegt im Programm in der Tabelle VPDID, einzige "Spalte" (und prim Key) ist "VPDID".
    Jede VPDID besteht aus einem bis vielen Betrieben, deren Betriebsnummer einem bestimmten Kontext entspricht, aber immer nur für den Einzelfall bekannt wird. Jede Betriebsnummer darf in der Tabelle "Betrieb" (prim Key = Betriebsnummer, VPDID verweist auf Tabelle VPDID) nur einmal vorkommen.
    Jeder Betrieb hat einen Satz Stammdaten (Kunden, Teile etc.), allerdings können solche Stammdaten durchaus mehrfach vorkommen, in den Spezialtabellen wie Ersatzteilestamm kann der Verweis auf einen Betrieb durchaus mehrfach bestehen.

    Das Problem ist die Betriebsnummer, weil ich nicht weiß, wie ich den CRASH durch unsachgemäße Handhabung verhindern soll. Bei sachgemäßer Handhabung passiert ohnehin nur was passieren soll. Leider kenne ich meine Pappenheimer ...
    Ich müsste die Angaben im dgv die zur Betriebsnummer gemacht werden also in jedem Fall prüfen können bevor sie an das DataSet weitergericht werden.

    HaRo
    ah - Validierung!
    Dann warste mit CellValueChanged schon rel. nahe dran - zur Validierung gibts ein CellValidating - Event.
    Darin kannste iwelche Tests machen, und ggfs. mit e.Cancel die Zell-Änderung ablehnen.

    Das geht so einfach aber nur, wenn zunächstmal gültige Daten vorliegen.
    Aber bei dir scheinen die Daten zunächstmal ungültig zu sein, und die MA haben das Vergnügen, sie erst gültig machen zu müssen.

    Unter solchen Umständen wächst sich die Validiererei schnell zu einer eigenen Wissenschaft aus.
    Ansatzpunkt ist das Setzen der .RowError-Property im _RowChanged-Event der typisierten DataTable.

    Tja - habichmich bisher nur in c# mit beschäftigt: IDataErrorInfo

    Validiererei erfordert ordentliches Grundlagenwissen: Events, Interfaces, Klassenhierarchie, und die Unterscheidung von "harter" und "weicher" Validierung.
    Die GrundIdee weicher Validierung ist, dass der Datensatz selbst weiß, dasser ungültig ist.

    mit c# klarzukommen ist eher noch ein minor-problem.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Hallo Erfinder,

    ich mach mir da ein paar Gedanken zu. Hauptberuflich war ich nie Programmierer, hatte mich aber schon mal zu den C/PM Endzeiten und DOS Anfangstagen mal eine Weile mit C beschäftigt. Grundsätzlich bin ich trotz meines Alters ja noch lernfähig :)
    Ich dachte auch schon daran, die Eingabe und Validierung der Betriebsnummer selbst ausschliesslich über eine Textbox laufen zu lassen mit einer Schaltfläche aus Auslöser der Validierung. Dann kann ich auch erfassen, ob es die Betriebsnummer schon gibt, bevor das DataSet etwas von dem möglichen neuen Datensatz etwas "weiß" und ich muss mich nicht knietief in C# bewegen ;)

    Bis Freitag bin ich jetzt ohnehin erst einmal weg, dann werde ich das Thema wie üblich am WE aufarbeiten.

    Danke Dir,
    HaRo
    naja - es ist vlt. nicht sooo wild. Ein "einfaches" Methödchen:

    Quellcode

    1. void CustomerDataTable_RowChanging(object sender, System.Data.DataRowChangeEventArgs e) {
    2. if (!_ObservedActions.Contains(e.Action )) return;
    3. CustomerRow rw = (CustomerRow)e.Row;
    4. if (rw.HasErrors) rw.ClearErrors();
    5. if (rw.IsCustomerNameNull()) rw.SetColumnError("CustomerName", "please input Name!");
    6. else if (rw.CustomerName.Length < 4) rw.SetColumnError("CustomerName", "Name too short!");
    7. if (!rw.IsCustomerPhoneNull() && !_PhoneRgx.IsMatch(rw.CustomerPhone)) {
    8. //obige Bedingung greift, wenn nichtNull eingegeben wurde, aber der Regex scheitert
    9. rw.SetColumnError("CustomerPhone", "Phone not valid!");
    10. }
    11. }
    - wie gesagt: wenn ungültig, dann DataRow.SetColumnError(blabla), und schon hast du ein DGV mit ErrorProvidern in den Zellen, wie die Bildle weiter unten in dem Tut zeigen.

    Zusätzlich noch DataSet.HasErrors abfangen, allerallerspätestens beim Abspeichern.

    Weil weiche Validierung schön und gut, und nett zum User, aber wenner nicht hört, muß am Ende Knüppel kommen.
    Halle Erfinder,

    ja ich nutze CellValidating um die Zellinhalte zu prüfen und um die Fehlerausgabe zu setzen und CellEndEdit und die Fehlerausgabe zu löschen.
    Das setzt aber das Editieren der Zellen voraus. Solange der anwender "nur" einen Datensatz nach dem anderen zufügt wird hier kein Ereignis geworfen.

    DataRowChangeEventArgs ist ja kein Event. Mein Problem ist, dass ich kein Event abfangen kann.
    Die BindingSource "BetriebSource"-Ereignisse werfen ebenfalls keine Ausnahme.

    Ich komme so nicht weiter, also werde ich das Zufügen von Datensätzen im DGV unterbinden und den Umweg über eine Textbox machen.
    Deren Inhalt kann ich validieren und sofern grundsätzlich valide kann ich dann versuchen im DatenSet mit

    VB.NET-Quellcode

    1. Me.KonverterSetting.Betrieb.FindByBetriebsnummer
    prüfen, ob es den Eintrag nicht doch schon gibt.


    Danke für Deine Hilfe.

    HaRo