Laden...

Entity Framework => MS SQL-Server Tabellenabhängigkeit

Erstellt von MMCSharp vor einem Jahr Letzter Beitrag vor einem Jahr 937 Views
M
MMCSharp Themenstarter:in
84 Beiträge seit 2022
vor einem Jahr
Entity Framework => MS SQL-Server Tabellenabhängigkeit

Verwendetes Datenbanksystem: <Entity Framework mit MSSQL_Server ->

Hallo Zusammen, ich habe den im Anhang befindlichen Datenbankaufbau. Nun würde ich gern einen Datensatz hinzufügen, jedoch habe ich Schwierigkeiten mit der Tabelle "Gates". Es werden zwar ID`s beim Erstellen des Adress- Datensatz für die "Places" erstellt, jedoch nicht für die "Gates" , was in einem Error endet.


                //Gates
                List<Gate> GateList = new List<Gate>();
                GateList.Add(new Gate() { Ramp = "Rampe 1" });
                GateList.Add(new Gate() { Ramp = "Rampe 2" });
                GateList.Add(new Gate() { Ramp = "Rampe 3" });

                //Places
                List<Place> PlaceList = new List<Place>();
                PlaceList.Add(new Place() { Area = "Halle 1", Gates = GateList });

                //Add completely Dataset
                var AddAddress = UoW.Addresses.Add(new Address { City = "Stadt", HouseNumber = "4", Postcode = "08151", StockName = "Stock 1", Street = "Straße", Place = PlaceList});

Wie kann ich "Gates" das korrekt anbinden beim Add?

2.079 Beiträge seit 2012
vor einem Jahr

Du gibst nirgendwo eine ID an.
Wenn Du die IDs nicht generieren lässt, musst Du sie selber angeben.
Oder Du lässt sie generieren:

https://docs.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=fluent-api#explicitly-configuring-value-generation

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

M
MMCSharp Themenstarter:in
84 Beiträge seit 2022
vor einem Jahr

Die ID`s werden via autoincrement in der Datenbank generiert, im Model habe ich das so angegeben:


public class Address
    {
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        [Required]
        public int Id { get; set; }
        public string StockName { get; set; }
        public string Postcode { get; set; }
        public string City { get; set; }
        public string Street { get; set; }
        public string HouseNumber { get; set; }

        public virtual ICollection<Place> Place { get; set; }
    }

M
MMCSharp Themenstarter:in
84 Beiträge seit 2022
vor einem Jahr

Entschuldigung!
Es war wohl eine Unstimmigkeit im Context zur Datenbank, jetzt klappt erstellen einer Address und einem Place, aber mehre Places mit mehreren Gates boykottiert er!

:


//Gates
                List<Gate> GateList = new List<Gate>();
                GateList.Add(new Gate() { Ramp = "Rampe 1" });
                GateList.Add(new Gate() { Ramp = "Rampe 2" });
                GateList.Add(new Gate() { Ramp = "Rampe 3" });

                //Places
                List<Place> PlaceList = new List<Place>();
                PlaceList.Add(new Place() { Area = "Halle 1", Gates = GateList});
                PlaceList.Add(new Place() { Area = "Halle 2", Gates = GateList});
                PlaceList.Add(new Place() { Area = "Halle 3", Gates = GateList});

                //Add compleate Dataset
                var AddAddress = UoW.Addresses.Add(new Address { City = "Stadt", HouseNumber = "4", Postcode = "08151", StockName = "Stock 1", Street = "Straße", Place = PlaceList});

Fehlermeldung:
System.InvalidOperationException: "Multiplicity constraint violated. The role 'Gate_Place_Target' of the relationship 'EFCoreToMSSQLS.Gate_Place' has multiplicity 1 or 0..1."

16.835 Beiträge seit 2008
vor einem Jahr

Der Fehler passiert, wenn man eine Entität dem Kontext mehrmals bekannt macht. Das kann explizit passieren oder implizit, wenn Du mit dem EF Verhalten falsch umgehst.
Findest abertausende Google Treffer dazu.

Meine erste Vermutung wäre, dass das Vorgehen an für sich halt sehr sehr ungünstig ist, man so halt kein EF Code schreibt.
I.d.R. fügt man eine neue Entität dem Context hinzu, bevor man diese einer anderen Entität zuweist, um das Attachment-Verhalten zu erreichen.


// Gate instantiieren
// Gate dem Kontext hinzufügen
// Gate der Liste hinzufügen
// Place erstellen
// Gates zuweisen

Bei Dir passiert das alles vollständig implizit und auf Basis "Wird schon irgendwie EF hinbekommen" - tut es halt leider nicht immer.
Und ich geh auch davon aus, dass der kaskadierte Graph der Fehler ist (bei Dir wird quasi jedes Gate versucht mehrfach hinzuzufügen).

Auch wenn das EF es einem einfach macht, Business Modelle als Datenbank-Modelle zu nutzen, sollte man das nicht tun.
A) weil eine Business Logik i.d.R. andere Strukturen hat als ein gutes DB-Schema B) weil man Entitäten in ihrem Lebenszyklus teilweise anders handhaben muss als Modelle.
Letzteres ist wahrscheinlich Ursprung dieses Fehlers.

Mach einfach mal das EF Tutorials in den Docs. Das zeigt Dir die Basics und wie man mit EF umgehen sollte.

Wenn Du des weiteren Hilfe willst, musst Du mehr Code zeigen - wir können alle nicht hellsehen.
Wir sehen hier zwar, wie Du mit Modellen arbeitest, aber nicht mit dem Kontext.

M
MMCSharp Themenstarter:in
84 Beiträge seit 2022
vor einem Jahr

Arg viel mehr Code gibt es nicht. Meine erstellte Anwendung dient zum erlernen. Der Aufbau geht mit einem Repository-Pattern über eine UnitOfWork. In einer Consolen- Anwendung erstelle ich Abfragen. Erstellt wurde die Datenbank mit Code-First (@Abt => Wie im anderen Tread besprochen )

UoW:


 public class MSSQLS_UnitOfWork : IUnitOfWork, IDisposable
    {
        private readonly MSSQLS_EFContext _context;

        public IGatesRepository Gates { get; set; }
        public IPlacesRepository Places { get; set; }
        public IEmptysRepository Emptys { get; set; }
        public IAddressRepsitory Addresses { get; set; }
        public ITruckRegistrationRepository TruckRegistration { get; set; }

        public MSSQLS_UnitOfWork(MSSQLS_EFContext context)
        {
            _context = context;
            Gates = new MSSQLS_GatesRepository(_context);
            Places = new MSSQLS_PlacesRepository(_context);
            Emptys = new MSSQLS_EmptysRepository(_context);
            Addresses = new MSSQLS_AddressRepository(_context);
            TruckRegistration = new MSSQLS_TruckRegistrationRepository(_context);
        }

        public async Task CompleteAsync()
        {
            await _context.SaveChangesAsync();
        }

        public void Dispose()
        {
            _context.Dispose();
        }
  }

Address- Repository:


 public class MSSQLS_AddressRepository : MSSQLS_Repository<Address>, IAddressRepsitory
    {
        public MSSQLS_AddressRepository(MSSQLS_EFContext context) : base(context)
        {
        }

    }

Repository:


    public class MSSQLS_Repository<T> : IRepository<T> where T : class
    {
        protected MSSQLS_EFContext _context;
        internal DbSet<T> dbSet;


        public MSSQLS_Repository(MSSQLS_EFContext context)
        {
            _context = context;
            dbSet = context.Set<T>();
        }

        public virtual T Add(T t)
        {
            _context.Set<T>().Add(t);
            _context.SaveChanges();
            return t;
        }

        public virtual async Task<T> AddAsync(T t)
        {
            _context.Set<T>().Add(t);
            await _context.SaveChangesAsync();
            return t;
        }

        public int Count()
        {
            return _context.Set<T>().Count();
        }

        public async Task<int> CountAsync()
        {
            return await _context.Set<T>().CountAsync();
        }

        public virtual void Delete(T entity)
        {
            _context.Set<T>().Remove(entity);
            _context.SaveChanges();
        }

        public virtual async Task<int> DeleteAsync(T entity)
        {
            _context.Set<T>().Remove(entity);
            return await _context.SaveChangesAsync();
        }

        private bool disposed = false;
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    _context.Dispose();
                }
                disposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public virtual T Find(Expression<Func<T, bool>> match)
        {
            return _context.Set<T>().SingleOrDefault(match);
        }

        public ICollection<T> FindAll(Expression<Func<T, bool>> match)
        {
            return _context.Set<T>().Where(match).ToList();
        }

        public async Task<ICollection<T>> FindAllAsync(Expression<Func<T, bool>> match)
        {
            return await _context.Set<T>().Where(match).ToListAsync();
        }

        public virtual async Task<T> FindAsync(Expression<Func<T, bool>> match)
        {
            return await _context.Set<T>().SingleOrDefaultAsync(match);
        }

        public virtual IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
        {
            IQueryable<T> query = _context.Set<T>().Where(predicate);
            return query;
        }

        public virtual async Task<ICollection<T>> FindByAsync(Expression<Func<T, bool>> predicate)
        {
            return await _context.Set<T>().Where(predicate).ToListAsync();
        }

        public T GetById(int id)
        {
            return _context.Set<T>().Find(id);
        }

        public virtual async Task<T> GetByIdAsync(int id)
        {
            return await _context.Set<T>().FindAsync(id);
        }

        public IQueryable<T> GetAll()
        {
            return _context.Set<T>();
        }

        public virtual async Task<ICollection<T>> GetAllAsync()
        {
            return await _context.Set<T>().ToListAsync();
        }

        public IQueryable<T> GetAllIncluding(params Expression<Func<T, object>>[] includeProperties)
        {
            IQueryable<T> queryable = GetAll();
            foreach (Expression<Func<T, object>> includeProperty in includeProperties)
            {

                queryable = queryable.Include(includeProperty);
            }

            return queryable;
        }

        public virtual void Save()
        {
            _context.SaveChanges();
        }

        public async virtual Task<int> SaveAsync()
        {
            return await _context.SaveChangesAsync();
        }

        public virtual T Update(T t, object key)
        {
            if (t == null)
                return null;
            T exist = _context.Set<T>().Find(key);
            if (exist != null)
            {
                _context.Entry(exist).CurrentValues.SetValues(t);
                _context.SaveChanges();
            }
            return exist;
        }

        public virtual async Task<T> UpdateAsync(T t, object key)
        {
            if (t == null)
                return null;
            T exist = await _context.Set<T>().FindAsync(key);
            if (exist != null)
            {
                _context.Entry(exist).CurrentValues.SetValues(t);
                await _context.SaveChangesAsync();
            }
            return exist;
        }
    }

Address Model:


public class Address
    {
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        [Required]
        public int Id { get; set; }
        public string StockName { get; set; }
        public string Postcode { get; set; }
        public string City { get; set; }
        public string Street { get; set; }
        public string HouseNumber { get; set; }

        public virtual ICollection<Place> Place { get; set; }
    }

DbContext:


public class MSSQLS_EFContext : DbContext
    {
        public DbSet<Address> Addresses { get; set; }
        public DbSet<Place> Places { get; set; }
        public DbSet<Gate> Gates { get; set; }
        public DbSet<TruckRegistration> TruckRegistrations { get; set; }
        public DbSet<Empty> Emptys { get; set; }
    }

16.835 Beiträge seit 2008
vor einem Jahr

Es fehlt aber in Deinem Code, wie mit dem Context umgegangen wird, SaveChanges.
Wenn Du da nichts hast (Du also alles implizit von EF machen lässt, vor allem das Graph Handling), so funktioniert das leider in dem Fall nicht.
Das automatische Handling von Sub-Entities funktioniert nur, wenn sie einen Weg haben und nicht mehrere wie bei Dir
https://docs.microsoft.com/en-us/ef/core/saving/related-data
Du musst das machen, was ich oben als Schritte erklärt hab.

Ansonsten Feedback zum Code: das ist nicht wirklich die Idee hinter UoW und Repository Pattern.


public class MSSQLS_UnitOfWork : IUnitOfWork, IDisposable
   {
       private readonly MSSQLS_EFContext _context;

       public IGatesRepository Gates { get; set; }
       public IPlacesRepository Places { get; set; }

Der UnitOfWork ist der DbContext - nicht die Logik.
Der UoW hat im Endeffekt die Aufgaben: DB Verbindung, Strukturzugriff und Commits. Aber er kennt keine Repositories, Logik oder Queries.

Der UoW sollte die Repositories nicht kennen, sondern umgekehrt. In diesem Fall wäre MSSQLS_UnitOfWork eher die Logikkomponente, da er die Repositories orchestriert werden. Auch sollte das DbSet nicht im Repository selbst, sondern im Context erzeugt werden. Eigentlich musst Du nur das machen, was die Doku auch aussagt.
Wo hast Du denn den Beispielcode her? Weil aus den offiziellen Docs ist das nicht.

Man sieht schon sehr früh am Anfang, wie man einen Kontext aufbauen sollte:
https://docs.microsoft.com/ef/core/modeling/?WT.mc_id=DT-MVP-5001507


internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
}

Weiterhin ist das Repository nicht für das Speichern verantwortlich


    public virtual T Add(T t)
    {
        _context.Set<T>().Add(t);
        _context.SaveChanges();
        return t;
    }

Wann gespeichert wird, das entscheidet die Logik. Das Repository ist für das Manipulieren des Context da.
Bei Dir wäre jede Context-Aktion automatisch auch ein Speichern und steht damit im Widerspruch zu Transaktionen.

Beispiele:

Für das Speichern wie gesagt, das muss in der Logik passieren
AspNetCoreEnterprisePlatform/TenantAddCommandHandler.cs · BenjaminAbt/AspNetCoreEnterprisePlatform

M
MMCSharp Themenstarter:in
84 Beiträge seit 2022
vor einem Jahr

Ist das so in Ordnung, einen Löschvorgang mit:


var Deleted = Task.Run(async()=> await UoW.Addresses.Delete(AddAddress));

aufzurufen?

Der Task:


 public virtual Task Delete(T entity)
        {
            _context.Set<T>().Remove(entity);
            return Task.CompletedTask;
        }

2.079 Beiträge seit 2012
vor einem Jahr

Nein, das ist Quatsch.

Du hast eine Delete-Methode, die einen CompletedTask zurück gibt und rufst sie einem neuen Task auf.
Heißt effektiv: Du hast eine synchron arbeitende Delete-Methode, die Du in einen Task steckst und die Beendigung nicht abwartest.
Auch die "Deleted"-Variable ist falsch benannt, denn das ist ein Task.

Wenn Du asynchron arbeiten willst, dann nutze die asynchronen Methoden, die EFCore dir zur Verfügung stellt.
Wenn Du eigene asynchrone Methoden entwickeln willst, solltest Du dich dringend vorher ausführlich damit auseinander setzen, das ist kein leichtes Thema.

Und mir wird auch nicht ganz klar, wie das mit deiner ursprünglichen Frage zusammenhängt.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

16.835 Beiträge seit 2008
vor einem Jahr

Leider nein. Macht absolut kein Sinn.
Das ist auch weder die Funktionsweise von Delete noch entspricht sie dem, wie async/await funktioniert / die Idee dahinter.

Delete ist im Gegensatz zu Add keine IO-Operation, entsprechend gibt es auch keine asynchrone Methode in EF dazu.
Erst das Speichern ist ein IO-Vorgang, weshalb dies asynchron erfolgen sollte. Ergo macht es keinen Sinn, dass Delete so in einem Task aufgeführt wird - das ist kontraproduktiv.
Siehe mein Beispiel Code aus dem Repo von oben.