asp.net Core MVC doppelte daten

salmi86

Cadet 1st Year
Registriert
Jan. 2023
Beiträge
10
Hallo,

wie kann ich vermeiden das ein Eintrag doppelt vorhanden ist ?

Hier mein bisheriger code:
C#:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
using System.Linq;
using UserManagement.MVC.Data;
using UserManagement.MVC.Enums;
using UserManagement.MVC.Models;

namespace UserManagement.MVC.Controllers
{
    [Authorize(Roles = "Admin")]
    public class MitgliederController : Controller
    {

        private readonly ApplicationDbContext _context;

        public MitgliederController(ApplicationDbContext context)
        {
            _context = context;
        }
        public IActionResult Index()
        {
            System.Collections.Generic.List<Mitglieder> Mitglieder = _context.Mitglieder.ToList();

            ViewBag.Mitglieder = Mitglieder;

            return View();
        }
        public IActionResult Bearbeiten(int id)
        {
            if (id == 0)
            {
                return View("MitgliederHinzu");
            }

            var MitgliederInDb = _context.Mitglieder.Find(id);

            if (MitgliederInDb == null)
            {
                return NotFound();
            }

            return View("MitgliederHinzu", MitgliederInDb);
        }

        [HttpPost]
        public IActionResult MitgliederHinzu(Mitglieder Mitglieder)
        {
            if (Mitglieder.Id == 0)
            {
                _context.Mitglieder.Add(Mitglieder);
            }
            else
            {
                _context.Mitglieder.Update(Mitglieder);
            }

            _context.SaveChanges();

            return RedirectToAction("Index");
        }

        public IActionResult MitgliederLoeschenById(int id)
        {
            var MitgliederInDb = _context.Mitglieder.Find(id);

            if (MitgliederInDb == null)
            {
                return NotFound();
            }

            _context.Mitglieder.Remove(MitgliederInDb);
            _context.SaveChanges();

            return RedirectToAction("Index");
        }
    }
}
 
@salmi86 Ich nehme an, du meinst doppelte Einträge beim Hinzufügen eines Mitglieds?
Indem du vor dem Hinzufügen in deiner Mitgliedertabelle suchst, ob es das Mitglied schon gibt. Normalerweise gibt es dazu eindeutige Merkmale, wie Vorname+Nachname, E-Mail Adresse oder Loginname.
 
@DocWindows JA das habe ich gemeint ! könntest du mir hierfür eventuell bitte ein code Beispiel senden ?
Ich habe erst vor wenigen Tagen begonnen mich als Programmierer zu versuchen "Bin am Lernen" ;)

Vielen Dank
 
Zum Beispiel indem du das _context.Mitglieder.Add(..) in ein if kapselst, welches auf einen doppelten Eintrag prüft

Code:
if (_context.Mitglieder.Where(m => m.Vorname == mitglied.Vorname && m.Nachname == mitglied.Nachname) == null)
{
   _context.Mitglieder.Add(mitglied)
}
else
{
   // doppelt
}


Achtung: Ich sitze gerade in der Mittagspause am Handy. Obigen Code nötigenfalls auf die korrekten Variablennamen anpassen.
 
wäre es nicht sinnvoller das Feld als Schlüssel in der Datenbank zu setzen? Dann darf die sich darum kümmern.
 
Das ist irgendwas hinter ApplicationDbContext was dann auf die Mitglieder geht, schau dir dein Datenmodell für die Datenbank an. Schlüsselwörter wären Primary Key oder wenn du das nicht ohne weiteres ändern kannst, Constraints. Bin kein Experte für .Net, meine C# Zeiten liegen viele Jahre zurück.

In diesem Fall würdest du dann einen Fehler von der Datenbank bekommen, Hey das was du machst geht nicht, da Kombination xy bereits vorhanden. Den Fehler kannst du dann Aufbereiten und dem User präsentieren.
Für solche Fälle arbeite ich gerne Ressourcenschonend, und werf nicht mit unnützen Queries um mich. (Wenn du irgendwann mal eine Datenbasis mit mehreren TB in der Hand hast, wirst du verstehen was ich meine)

Wenn deine SQL Kenntnisse noch nicht so gut sind, empfehle ich dir dich erst einmal Grundlegend mit Datenbanken zu beschäftigen. ORM Mapper können danach noch immer genutzt werden. Nur lernst du dadurch wie es funktioniert, was schlecht und gut ist. Wahrscheinlich sind Datenbanken für den Anfang sowieso eine Nummer zu groß. Persönlich würde ich erst einmal das Prinzip mit Dateien empfehlen. Kannst dort z.B. mit JSON auch Strukturen etc. aufbauen. So lang du das zum lernen nutzt und es deinen Rechner nicht verlässt, ausreichend.
 
@Raijin

habe es versucht aber es funktioniert nicht !
Stimmt der code so ?
Code:
        [HttpPost]
        public IActionResult MitgliederHinzu(Mitglieder Mitglieder)
        {
            if (Mitglieder.Id == 0)
            {
                if (_context.Mitglieder.Where(m => m.Vorname == Mitglieder.Vorname && m.Nachname == Mitglieder.Nachname) == null)
                {
                    _context.Mitglieder.Add(Mitglieder);
                }
                else
                {
                }
            }
            else
            {
                _context.Mitglieder.Update(Mitglieder);
            }

            _context.SaveChanges();

            return RedirectToAction("Index");
        }
 
@salmi86 Was genau funktioniert da nicht? Hat dein Mitglieder-Objekt denn Vorname und Nachname als Property? Es ist Beispielcode den du auf deine Situation anpassen musst.

Unter welchen Umständen würdest du denn ein Mitglied als "doppelt" ansehen? Was muss erfüllt sein?
Darauf prüfst du dann die Datenbank.

Angenommen du sagst dass es keine 2 Mitglieder mit gleichem Vor- und Nachnamen in der gleichen Abteilung geben darf, weil das sonst "doppelt" wäre, kannst du das mit dem Datenbankkontext prüfen.

Code:
var dbMitglied = _context.Mitglieder.Where(p=>p.Vorname.ToLower() == Mitglieder.Vorname.ToLower() &&
p.Nachname.ToLower() == Mitglieder.Nachname.ToLower() && 
p.Abteilungsnummer == Mitglieder.Abteilungsnummer).FirstOrDefault();

if (dbMitglied  == null)
{
// Das Mitglied mit diesem Vor- und Nachnamen gibt es in der Abteilung noch nicht
}
else 
{
// Das Mitglied gibt es hier schon
}

Für "schlüsselfertigen" Code den du einfach per Copy&Paste einfügen kannst, hast du viel zu wenige Informationen geliefert.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Raijin
Natürlich ist das nur Beispielcode gewesen, den ich wie gesagt am Handy zusammengekritzelt habe. Du solltest dies aber als Basis nutzen und für deine Zwecke anpassen können. @DocWindows hat beispielsweise die Problematik der Groß-/Kleinschreibung durch Ergänzung um .ToLower() gelöst.

Letztendlich steht und fällt das ganze aber natürlich mit deiner Datenbasis. Wenn die Tabelle der Mitglieder lediglich aus Id und Name besteht, ist eine Prüfung auf Vorname und Nachname natürlich sinnfrei.


Prinzipiell ist das auch nur eine von mehreren möglichen Lösungen. Ich habe mich bei dem Vorschlag lediglich an die gleiche Methode gehalten wie es bereits in der Löschen-Funktion gemacht wurde. Dort wird ebenfalls die Mitglieder-Liste im context auf ein vorhandenes Element geprüft, in diesem Fall eben nur anhand der Id.

Auch der Vorschlag von @Testa2014 ist legitim und streng genommen sogar der bessere Weg. Die Definition der Datenbasis inkl. etwaiger Einschränkungen wie eindeutige Namen und dergleichen obliegt eigentlich der Datenbank. Löst man dies ausschließlich über das FrontEnd, könnte man ggfs von Hand fehlerhafte Eingaben in der Datenbank eintragen und die Software würde es am Ende gar nicht merken, sondern ggfs Fehler produzieren, weil stets der falsche doppelte Eintrag verwendet wird (zB immer der erste).



Für bessere Vorschläge und/oder präzisere Lösungen musst du uns daher mehr Informationen geben. Dazu zählt u.a. die Tabellendefinition für "Mitglieder" sowie natürlich auch die eingesetzte Datenbank, sei es MS SQL Server, MySQL, Postgresql oder auch ganz banal SQLite oder was auch immer.



Noch ein kleiner Tip:
Du verwendest an 3 Stellen die Bezeichnung "Mitglieder". Scheinbar gibt es im context eine Liste von Mitgliedern - context.Mitglieder - aber es gibt auch eine Klasse "Mitglieder" und dann verwendest du auch noch "Mitglieder" als Namen für eine Instanz von "Mitglieder".

Code:
public void IrgendwasMitEinemMitglied (Mitglieder Mitglieder)
{
   context.Mitglieder.Find(Mitglieder.Id)
}

Das ist schlechter Stil. Die Klasse "Mitglieder" enthält mutmaßlich nur Informationen zu EINEM Mitglied, sollte daher auch "Mitglied" heißen. Eine Variable von diesem Typ sollte nicht identisch benannt werden, also dann eher zB kleingeschrieben "mitglied". Im context wird es eine Liste sein und da stimmt der Name.
Besser wäre es also so:

Code:
public void IrgendwasMitEinemMitglied (Mitglied mitglied)
{
   context.Mitglieder.Find(Mitglieder.Id)
}

Sofern du mit Visual Studio arbeitest, kannst du mit "STRG+R+R" etwas umbenennen, wenn der Cursor draufsteht.
 
Hallo,

finde euch echt toll, die Hilfe hier ist TOP!!

Wenn ich das Beispiel einsetze und anpasse kann ich gar kein Mitglied mehr eintragen.
Ich verwende Microsoft SQL Server.
Meine Mitglieder DB ist wie folgt aufgebaut.
db.png

Ich möchte dass geprüft wird ob das Mitglied vorhanden ist (Vorname, Nachname) wenn ja soll eine Meldung erscheinen MITGLIED BEREITS VORHANDEN ! und wenn nicht soll das Mitglied angelegt werden.

Danke,
LG Stefan
 
Dann sollte das aber eigentlich so funktionieren wie gezeigt. Es wird vor dem Hinzufügen geprüft ob in "Mitglieder" bereits ein Element vorhanden ist, das den Vor- bzw Nachnamen enthält.

Wenn das nicht klappt, musst du im Debug-Modus Schritt für Schritt durchgehen und schauen was passiert - zB was bei der Prüfung auf das Vorhandensein des Mitglieds zurückgegeben wird. Normalerweise sollte da NULL rauskommen, wenn es in der Liste kein Mitglied mit den genannten Bedingungen (Vor- und Nachname) gibt. Gibt es bereits ein Mitglied, wird entsprechend eine Instanz von "Mitglied" mit den Parametern des jeweiligen Mitglieds zurückgegeben, die uns aber egal sind, weil wir ja nur prüfen wollen ob es das Mitglied schon gibt oder nicht.
 
ich hoffe du hast auch den Fall bedacht, das es Leute mit genau dem gleichen Namen gibt?

Sonst verweise ich mal auf ein Tutorial bei MS, im Bezug auf Constraints. Unten hast du auch weitere Links mit denen du das definitiv meistern kannst :) https://learn.microsoft.com/en-us/sql/relational-databases/tables/create-check-constraints

Auch würde es den Kollegen hier helfen, wenn du deinen aktuellen Code nochmal teilen würdest. Das Datenmodell ist jetzt bekannt.
 
Wenn man es über die Datenbank lösen möchte, was konzeptionell der bessere Weg ist, weil nur dann sichergestellt ist, dass ungeachtet der Art der Eingabe die Tabelle stets den Regeln entspricht, kann man der Tabelle "Mitglieder" ein unique constraint hinzufügen.

SQL:
ALTER TABLE Mitglieder
ADD CONSTRAINT UQ_Mitglieder UNIQUE (Vorname,Nachname);

Damit fügt man in MS SQL eine Einschränkung ein, die darauf basiert, dass die Kombination aus Vor- und Nachname einzigartig sein muss. @Testa2014 hat es aber schon geschrieben, dass man sich dabei natürlich sicher sein muss, dass es wirklich keinen zweiten "Peter Parker" gibt. Gegebenenfalls wäre es also auch denkbar, die Einschränkung um das GebDatum zu erweitern - bis dann ein zweiter Peter Parker daherkommt, der am selben Tag geboren wurde, was beliebig unwahrscheinlich ist, aber theoretisch möglich wäre.

Wenn man mit so einem constraint arbeitet, muss man sich jedoch bewusst sein, dass die Datenbank dann das Einfügen des neuen Mitglieds verhindert, egal wie das neue Mitglied eingetragen wird - sei es über die Anwendung oder zB von Hand über das SQL Management Studio. Auch muss man dann den Code etwas anders gestalten, weil context.SaveChanges() eine Exception wirft, wenn ein Verstoß gegen die constraints festgestellt wurde - hier: doppeltes Mitglied. Das heißt, dass man mit try.. catch arbeiten sollte, weil sonst eine unbehandelte Exception kommt, die ggfs die ganze Anwendung abschmieren lässt.

Das sähe dann in etwa so aus:

C#:
try
{
   context.Mitglieder.Add(neuesMitglied);
   context.SaveChanges(); // <-- Wirft bei Fehler eine Exception, die in den catch-Block leitet
   Debug.WriteLine("Mitglied hinzugefügt!"); // Passiert nur OHNE vorherige Exception
}
catch (Exception e)
{
   // Hier landet man bei einem Fehler
   Debug.WriteLine("Fehler beim Hinzufügen : {0}", e.Message);
}
// Hier geht's weiter im code - mit oder ohne Exception...


Nachtrag:
Du verwendest in der Tabelle für alle Strings nvarchar(MAX). Dabei kalkuliert die Datenbank den String mit seiner Maximallänge ein. Der Speicher wird zwar nicht direkt reserviert, aber es ist dennoch unnötig für zB eine Telefonnummer, die vielleicht 20 Zeichen lang ist, ein Feld mit der Kapazität einer Bibel zu verwenden. Macht man das in einer Tabelle an einer Stelle, ist das noch halb so wild, aber du hast sage und schreibe 18 Felder mit (MAX). Natürlich "funktioniert" das so, aber es ist unschön und man sollte sich beim Design von Datenbanken stets überlegen ob der verwendete Datentyp sinnvoll ist und dem Inhalt gerecht wird.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Testa2014
Hallo Leute,

danke mit dem UNIQUE Constraint funktioniert es ! KEIN DOPPELTER EINTRAG MÖGLICH !

Wie muss ich den Code erweitern damit auch das Bearbeiten der Bestehenden Mitglieder funktioniert ?
Aktueller Code:
Code:
        [HttpPost]
        public IActionResult MitgliederHinzu(Mitglieder Mitglieder)
        {
            try
            {
                _context.Mitglieder.Add(Mitglieder);
                _context.SaveChanges(); // <-- Wirft bei Fehler eine Exception, die in den catch-Block leitet
                Debug.WriteLine("Mitglied hinzugefügt!"); // Passiert nur OHNE vorherige Exception
            }
            catch (Exception e)
            {
                // Hier landet man bei einem Fehler
                Debug.WriteLine("Fehler beim Hinzufügen : {0}", e.Message);
            }
            // Hier geht's weiter im code - mit oder ohne Exception...

            return RedirectToAction("Index");
        }

Alter Code vor der dem UNIQUE Constraint

Code:
        [HttpPost]
        public IActionResult MitgliederHinzu(Mitglieder Mitglieder)
        {
            if (Mitglieder.Id == 0)
            {
                _context.Mitglieder.Add(Mitglieder);
            }
            else
            {
                _context.Mitglieder.Update(Mitglieder);
            }

            _context.SaveChanges();

            return RedirectToAction("Index");
        }

Vielen Dank nochmals !
LG Stefan
 
Das anzupassen sollte eigentlich mit dem Gezeigten nicht schwer sein. Ganz wichtig ist, dass du Dinge nachvollziehst, die du aus Tutorials, Videos oder Foren rausziehst. Bitte nie einfach blind abtippen, ohne zu wissen was du da eigentlich tust. Wenn das bedeutet, dass du bei google kurz mal "sql unique constraint" oder "c# try catch" eingibst, dann ist das eben so. Auf diese Weise vermeidest du nicht nur, dass dir jemand irgendeinen "Mist" unterjubelt, sondern du lernst auch, das Gezeigte falls nötig anzupassen.

try catch bzw. allgemein "Exception handling" ist eine wichtige Komponente beim Programmieren. Ignoriert man das und zur Laufzeit tritt ein Fehler auf - wie zB bei context.SaveChanges() wenn ein doppelter Eintrag erkannt wurde - dann gibt's ne Fehlermeldung und die Anwendung stürzt entweder komplett ab oder läuft weiter, aber ggfs in einem undefinierten Zustand.

Mit dem try catch sicherst du daher das Rückspeichern in die Datenbank ab, denn das tut context.SaveChanges(). Diese Anweisung übernimmt vorherige Änderungen (zB das .Add) und schiebt sie als SQL-Anweisung (zB INSERT) in die Datenbank - genau da tritt dann der Fehler bei doppelten Einträgen auf. Dasselbe gilt, wenn du ein .Update() ausführst und das darin übergebene Mitglied enthält nun einen geänderten, aber jetzt doppelten Namen. Heißt: Durch das unique (Vorname, Nachname) in der Tabelle kannst du auch kein Mitglied mehr umbenennen, also updaten, wenn der neue Name schon vorhanden ist.
Das context.SaveChanges() ist also das zentrale Element, das du mit try catch absichern musst. Ob du davor nun ein Mitglieder.Add oder .Update machst, spielt erstmal keine Rolle.

So könnte das also aussehen:

C#:
if (Mitglieder.Id == 0)
{
    // Id == 0 impliziert, dass das übergebene Mitglieder-Objekt keine gültige Id hat und somit "neu" ist.
    context.Mitglieder.Add(Mitglieder);
}
else
{
    // Id != 0 impliziert, dass das übergebene Mitglieder-Objekt bereits eine Id hat, also vorhanden ist.
    context.Mitglieder.Update(Mitglieder);
}

// An dieser Stelle ist context.Mitglieder erstmal temporär verändert worden

try
{
    // Jetzt wird VERSUCHT ("try"), die temporären Änderungen in die Datenbank zu speichern
    context.SaveChanges();
    Debug.WriteLine("Alles OK");
}
catch (Exception e)
{
   // Schlägt SaveChanges() mit einer Exception fehl, landet man hier.
    Debug.WriteLine("Irgendwas ist schief gelaufen : {0}", e.Message);
}

Wichtig ist an dieser Stelle auch, dass man versteht was das catch in diesem Fall konkret tut. Es reagiert auf JEDE Exception, die im try-Block ausgelöst wird, weil "Exception" die Basisklasse aller Exceptions ist, quasi ein Wildcard *. Man landet also im catch-Block wenn gegen ein constraint verstoßen wurde (zB doppelter Eintrag), aber auch wenn zB die Datenbank-Verbindung plötzlich offline ist. Möchte man also spezifische Rückmeldungen geben bzw. unterschiedlich auf die jeweiligen Exceptions reagieren, muss man die spezifische Exception catchen, die von context.SaveChanges() ausgelöst werden können. catched man allgemein nur "Exception", kann dort jeder beliebige ausgelöste Fehler drinstecken, die Meldung muss also ebenfalls allgemein sein.

Warum ich das so deutlich schreibe? Ganz einfach, würde man nun im catch eine Meldung rausgeben "Doppelter Eintrag", aber tatsächlich ist die Datenbank offline (oder etwas anderes hat geknallt), dann wäre die Meldung irreführend und du würdest den Fehler an der falschen Stelle suchen. Frei nach dem Motto
"WAAAAAAAH!!!!! Du kack Programm, es gibt Peter Parker noch nicht!!!!!"
Das hat wohl so ziemlich jeder Programmierer in seinen Anfangszeiten schon erlebt.

Ich empfehle dir, erstmal mal ein Anfänger-Buch für C# zu suchen und die Grundlagen durchzuarbeiten. Viel weiter als bis hierhin kann ein Forum nämlich kaum Hilfestellung leisten und ich vermute mal, dass der eine oder andere, der das hier liest, schon denkt, dass ich mir viel zu viel Mühe gemacht habe, das auch noch so ausführlich zu erklären... :lol:
 
  • Gefällt mir
Reaktionen: Testa2014
Danke echt TOP von dir @Raijin ! ,
Kann dieser Code auch einen Fehler auf meiner Razor Seite anzeigen ?
Habe leider mein Anfänger Buch noch nicht erhalten !!!!! ;)
 
Klar. Du könntest einfach ViewBag.Fehler="Hoppla, ein Fehler beim Hinzufügen"; in den catch-Block setzen und in deiner Page kannst du diesen Fehler anschließend mit @ViewBag.Fehler darstellen - zuvor natürlich die obligatorische Prüfung auf NULL, weil's sonst knallt, wenn es keinen Fehler gibt und ViewBag.Fehler dementsprechend nicht existiert... oder du fügst auch im try nach dem SaveChanges() zB ein ViewBag.Fehler = "ok"; hinzu. Das ist aber nur eine Möglichkeit von vielen, weil man beispielsweise auch über den ModelState einen Fehler zurückgeben kann.

Gerade bei ASP.NET bietet Visual Studio schon sehr viele fertige Templates, die schon allerhand Techniken zeigen. Erstelle eine neue View und wähle zB "create" als Vorlage und das dazugehörige Model (Mitglieder). Da tauchen dann beispielsweise Validationmessages auf, die getriggert werden, wenn man zB bei einem Property des Models vom Typ int im Formular keine Zahl, sondern "hallo" eintippt. Da taucht dann so ein roter Text unter dem Eingabefeld auf "Nur Zahlen erlaubt" oder so ähnlich. Das kann man auch anpassen und könnte bei der Validation der Namensfelder programmatisch eine Meldung einfügen, die auf den doppelten Namen hinweist. Für den Anfsng reicht aber der Weg über ViewBag ;)
 
Zurück
Oben