DatenModellierung Problem (Veranstaltung eines Vereins)

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 139 Antworten in diesem Thema. Der letzte Beitrag () ist von Nofear23m.

    Hi,
    also ich hab nun mit dem Model etwas rum gespielt.
    Die Datenbank ist im Grunde ja schon da, auch beim Anwender.
    Daher hab ich das Datenmodell noch einmal abgeändert, klingt für mich plausiebler.
    Ich habe eine Klasse Organisation hinzugefügt, weil ich im alten Modell irgendwie keine wirkliche Zuordnung vom Responsible zur Organisation hatte da ja beides Persons waren.
    klappt aber wunderbar mit der Seed Methode.
    Im Grunde gibt es eine Person, die die Organisation verwaltet, diese kann zusätliche Responsible erstellen und diese Wiederum können Events und Seller erstellen.
    wird ein Responsible gelöscht, dürfen die dazugehörigen Events nicht gelöscht werden, da muss ich noch schauen ob das klappt.
    Ich lade nachher mal das Model Projekt hoch so wie es jetzt ist.
    "Hier könnte Ihre Werbung stehen..."

    MichaHo schrieb:

    wird ein Responsible gelöscht, dürfen die dazugehörigen Events nicht gelöscht werden, da muss ich noch schauen ob das klappt.

    Ich empfehle immer bei Datenbanken nur in Ausnahmefällen etwas zu löschen. Immer nur als gelöscht markieren und beim Abruf der Daten immer auf die "nicht als gelöscht markierten" zu Filtern.

    Es ist bei einer Datenbank echt nicht notwendig etwas zu löschen. Und der Vorteil das man etwas was man löscht wieder rückgängig machen kann ist enorm.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Puuuhhh, ich bin mir nicht mehr sicher aber hattest du nicht meine Model-Interfaces von meinem WPF Notes Projekt übernommen?
    Wenn ja, dort sind sie drinnen.

    Der Vorteil wenn man es mit Interfaces macht ist das man dem EF sogar beibringen kann das es bei Models welche dieses Interface implementieren das automatisch regelt.

    Sprich: Wenn z.b. das Model "Responsible" das, nennen wir es mal ILogicalDelete Interface implementiert und du löscht einen Responsible das EF vor dem zurückschreiben hergeht und sagt "Oh, das soll gelöscht werde, ne das ist eine Entität die nicht gelöscht werden soll, also mach ich das Rückgägngig und setze das IsDeleted Flag und den DeletedTimestamp anstatt den Datensatz zu löschen.

    So gehst du sicher das du nicht unabsichtlich etwas löscht.

    Weiters unterstützt EF Core seit ich glaube 2.1 auch Globale Filter, so das du auch nicht immer daran denken musst das du immer Filtern musst auf "IsDeleted = False" wenn du Daten abrufst. EF Core geht dann her und fügt bei jedem Abruf von der DB automatisch diesen Filter im SQL Query. Also auch da musst du dich nicht darum kümmern. Trotzdem kann man aber wenn man es bei einer abfrage nicht haben möchte jederzeit sagen das man diesen Filter nun nicht aktiv haben möchte. Sehr komfortable also und trotzdem immernoch flexibel.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Damit das auch automatisch funktioniert bitte nicht vergessen die SaveChanges und SaveChangesAsync Methoden des EF Core zu überschreiben, wie ich im Projekt. Musste dir hald übersetzen. Dort Manipuliere ich eben die Entitätszustände.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Hi,

    so, hab nun alles umgebaut.
    Das Model Projekt hänge ich mal an, irgendwie ist da noch der Wurm drinn.
    Hier mal die Seeding Methode:
    Spoiler anzeigen

    C#-Quellcode

    1. public void Seed()
    2. {
    3. Person organizationAdmnin = new Person()
    4. {
    5. FirstName = "Organisation",
    6. LastName = "Admin",
    7. Email = "admin@organisation.de",
    8. PersonCategory = PersonCategoryEnum.OrganizationAdmin,
    9. AccessData = new LogInData()
    10. {
    11. UserName = "orgaadmin",
    12. EncryptedPassword = GetMD5Hash("orgaadmin#2019"),
    13. IsInitialPassword = true,
    14. IsActive = true,
    15. Role = LogInDataRoleEnum.OrganisationAdmin
    16. },
    17. Organization = new Organization()
    18. {
    19. Name = "KiTa Kunterbunt Musterhausen e.v.",
    20. License = LicenseTypeEnum.Light,
    21. }
    22. };
    23. BazaarEvent event1 = new BazaarEvent();
    24. BazaarEvent event2 = new BazaarEvent();
    25. var responsibleSellerList = new List<Person>()
    26. {
    27. new Person()
    28. {
    29. FirstName = "Verantwortlicher",
    30. LastName = "Basar",
    31. Email = "verantwortlicher@basar.de",
    32. Organization = organizationAdmnin.Organization,
    33. PersonCategory = PersonCategoryEnum.EventResponsible,
    34. AccessData = new LogInData()
    35. {
    36. UserName = "responsible",
    37. EncryptedPassword = GetMD5Hash("res.ponsible#2019"),
    38. IsInitialPassword = true,
    39. Role = LogInDataRoleEnum.EventAdmin,
    40. IsActive = true
    41. }
    42. },
    43. new Person()
    44. {
    45. FirstName = "Verkäufer",
    46. LastName = "Nummer1",
    47. Email = "verkaeufer@nummer1.de",
    48. SellerNumber = 1,
    49. Organization = organizationAdmnin.Organization,
    50. PersonCategory = PersonCategoryEnum.EventSeller,
    51. AccessData = new LogInData()
    52. {
    53. UserName = "verkaeufer1",
    54. EncryptedPassword = GetMD5Hash("ver.kaeufer1#2019"),
    55. IsInitialPassword = true,
    56. IsActive = true,
    57. Role = LogInDataRoleEnum.Seller
    58. },
    59. PersonEvents = new List<PersonEvent>(){new PersonEvent() { BazaarEvent = event1} }
    60. },
    61. new Person()
    62. {
    63. FirstName = "Verkäufer",
    64. LastName = "Nummer2",
    65. Email = "verkaeufer@nummer2.de",
    66. SellerNumber = 2,
    67. Organization = organizationAdmnin.Organization,
    68. PersonCategory = PersonCategoryEnum.EventSeller,
    69. AccessData = new LogInData()
    70. {
    71. UserName = "verkaeufer2",
    72. EncryptedPassword = GetMD5Hash("ver.kaeufer2#2019"),
    73. IsInitialPassword = true,
    74. IsActive = true,
    75. Role = LogInDataRoleEnum.Seller
    76. },
    77. PersonEvents = new List<PersonEvent>(){new PersonEvent() { BazaarEvent =event2} }
    78. },
    79. new Person()
    80. {
    81. FirstName = "Verkäufer",
    82. LastName = "Nummer3",
    83. Email = "verkaeufer@nummer3.de",
    84. SellerNumber = 3,
    85. Organization = organizationAdmnin.Organization,
    86. PersonCategory = PersonCategoryEnum.EventSeller,
    87. AccessData = new LogInData()
    88. {
    89. UserName = "verkaeufer3",
    90. EncryptedPassword = GetMD5Hash("ver.kaeufer3#2019"),
    91. IsInitialPassword = true,
    92. IsActive = true,
    93. Role = LogInDataRoleEnum.Seller
    94. },
    95. PersonEvents = new List<PersonEvent>(){new PersonEvent() { BazaarEvent = event1, EventId = event1.ID} }
    96. }
    97. };
    98. var articleList = new List<Article>()
    99. {
    100. new Article() { ArticleNumber = 123, Description = "Spiel - Schnappt Hubi", Price = 2.5m, Quantity = 1, Person = responsibleSellerList.Find(s => s.SellerNumber == 1)},
    101. new Article() { ArticleNumber = 456, Description = "türkisblaue Jacke", Price = 5, Quantity = 1, Person = responsibleSellerList.Find(s => s.SellerNumber == 1) },
    102. new Article() { ArticleNumber = 789, Description = "bunte Hose", Price = 0.5m, Quantity = 1, Person = responsibleSellerList.Find(s => s.SellerNumber == 3)},
    103. new Article() { ArticleNumber = 1011, Description = "Häßliche Mütze", Price = 25, Quantity = 1, Person = responsibleSellerList.Find(s => s.SellerNumber == 2)},
    104. new Article() { ArticleNumber = 1012, Description = "Jacke", Price = 2, Quantity = 1, Size = "140/146", Person = responsibleSellerList.Find(s => s.SellerNumber == 3) },
    105. new Article() { ArticleNumber = 1013, Description = "Hose", Price = 4, Quantity = 1, Person = responsibleSellerList.Find(s => s.SellerNumber == 2) },
    106. new Article() { ArticleNumber = 1014, Description = "Turnhose", Price = 1, Quantity = 2, Person = responsibleSellerList.Find(s => s.SellerNumber == 1) },
    107. new Article() { ArticleNumber = 1015, Description = "Kaufladen mit Zubehör", Price = 25, Quantity = 1, Person = responsibleSellerList.Find(s => s.SellerNumber == 2) }
    108. };
    109. event1.EventName = "Kleiderbasar Musterhausen";
    110. event1.EventDate = DateTime.Now.AddDays(14);
    111. event1.Organization = organizationAdmnin.Organization;
    112. event1.ResponsiblePerson = responsibleSellerList.First(r => r.PersonCategory == PersonCategoryEnum.EventResponsible);
    113. event1.Provision = 10;
    114. event1.MinProvision = 5m;
    115. event1.MaxSellerNumber = (int)organizationAdmnin.Organization.License;
    116. event2.EventName = "Grillfeier KiTahausen";
    117. event2.EventDate = DateTime.Now.AddDays(30);
    118. event2.Organization = organizationAdmnin.Organization;
    119. event2.ResponsiblePerson = responsibleSellerList.First(r => r.PersonCategory == PersonCategoryEnum.EventResponsible);
    120. event2.MaxSellerNumber = 100;
    121. if (!Events.Any())
    122. {
    123. Events.Add(event1);
    124. Events.Add(event2);
    125. }
    126. if (!Persons.Any())
    127. {
    128. Persons.Add(organizationAdmnin);
    129. Persons.AddRange(responsibleSellerList);
    130. }
    131. if (!Articles.Any()) Articles.AddRange(articleList);
    132. SaveChanges();
    133. }
    134. internal string GetMD5Hash(string text)
    135. {
    136. if (string.IsNullOrEmpty(text)) return string.Empty;
    137. MD5 md5 = new MD5CryptoServiceProvider();
    138. byte[] Data = Encoding.ASCII.GetBytes(text);
    139. byte[] Result = md5.ComputeHash(Data);
    140. return BitConverter.ToString(Result);
    141. }

    Dateien
    "Hier könnte Ihre Werbung stehen..."

    MichaHo schrieb:

    irgendwie ist da noch der Wurm drinn

    Und wie äussert sich das? Verstehe nicht was nun nicht passt.
    Wo hast du probleme? Beim Seeding? Du hast "nur" das Model angehängt.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Hi,
    Problem ist eigentlich, das wenn ich dem Seller ein Person Event hinzufüge (im Seed). Dann wird mir in der PersonEvent Tabelle zusätzlich zur EventID eine BazaarEventID hinzugefügt und weis im Moment nicht, wo die zusätzliche BazarrEventID her kommt.
    Beim Model bin ich mir nicht sicher, ob die Relationen alle so passen.
    "Hier könnte Ihre Werbung stehen..."
    Ah, OK. Das hat mich nun auf die richtige spur geführt. Ich nehme mal an du hast per FLuent API (noch) nichts konfiguriert.

    MichaHo schrieb:

    Dann wird mir in der PersonEvent Tabelle zusätzlich zur EventID eine BazaarEventID

    Dafür steht vermutlich keine EventID drinnen sonder NUR die BazarEventId.

    Das liegt daran das EF Core dem Prinzip von "Convention over Configuration" folgt. Das bedeutet: Hast du im Model eine Navigation Property wie in diesem Fall BezaarEvent vom Typ BazaarEvent dann sagt EF "Oh, ich hab da eine Navigation, also benötige ich eine ID dafür.". Findet es keine ID welche per Konvention der Benamsung entspricht fügt es intern eine hinzu und bildet diese in der DB ab.

    Du hast eine EventID mit welche du diese Navigation verknüpfen willst. Da diese aber EventId und nicht BazaarEventId heißt, weis EF nicht das du dieses Property meinst. (Wie auch)
    Zwei möglichkeiten: Per Fluent API EF sagen das du dieses Property als Fremdschlüsselspalte definieren willst. (Configuration) oder das Property in BazaarEventId unbenennen. (Convention)

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Ahh, OK, ich Depp... hatte das EventModel in BazaarEvent umbenannt.
    Ich wollte das Datenmodell eigentlich so aufbauen:
    Organization = Haupttabelle
    diese hält eine Icollection von Person und eine Icollection von PersonEvent
    Die Person verweist auf die Organisation und hat auch eine Icollection von PersonEvent und eine Icollection von Article
    Die BazaarEvent verweist auf Organization und Person und hält eine Icollection von PersonEvent (Sellers)
    die Article verweist auf Person.
    In meiner Seed erstelle ich quasi von oben runter:
    die Organisation
    eine Liste von Personen, diese hat ne Liste von PersonEvent, dort einen event, dieses hat ne liste von Personevent (sellers) diese hält eine Person, diese person hat eine Liste von artikeln.
    sieht in Code so aus:
    Seed

    C#-Quellcode

    1. public void Seed()
    2. {
    3. Organization organization = new Organization()
    4. {
    5. Name = "KiTa Kunterbunt e.V.",
    6. License = LicenseTypeEnum.Light,
    7. OrganizationPersons = new List<Person>()
    8. {
    9. new Person()
    10. { FirstName = "Organisation",
    11. LastName = "Admin",
    12. Email = "admin@organisation.de",
    13. PersonCategory = PersonCategoryEnum.OrganizationAdmin,
    14. AccessData = new LogInData()
    15. {
    16. UserName = "admin",
    17. EncryptedPassword = GetMD5Hash("admin#2019"),
    18. IsInitialPassword = true,
    19. IsActive = true,
    20. Role = LogInDataRoleEnum.OrganisationAdmin
    21. }
    22. },
    23. new Person()
    24. {
    25. FirstName = "Verantwortlicher",
    26. LastName = "Basar",
    27. Email = "verantwortlicher@basar.de",
    28. PersonCategory = PersonCategoryEnum.EventResponsible,
    29. AccessData = new LogInData()
    30. {
    31. UserName = "responsible",
    32. EncryptedPassword = GetMD5Hash("res.ponsible#2019"),
    33. IsInitialPassword = true,
    34. Role = LogInDataRoleEnum.EventAdmin,
    35. IsActive = true
    36. },
    37. PersonEvents = new List<PersonEvent>()
    38. {
    39. new PersonEvent()
    40. { BazaarEvent = new BazaarEvent()
    41. {
    42. EventName = "Kleider Basar Musterhausen",
    43. EventDate = DateTime.Now.AddDays(14),
    44. Provision = 20,
    45. MinProvision = 5m,
    46. Sellers = new List<PersonEvent>()
    47. {
    48. new PersonEvent()
    49. {
    50. Person = new Person()
    51. {
    52. FirstName = "Claudia",
    53. LastName = "Muster",
    54. Email = "claudia@muster.de",
    55. PersonCategory = PersonCategoryEnum.EventSeller,
    56. SellerNumber = 1,
    57. AccessData = new LogInData()
    58. {
    59. UserName = "cmuster",
    60. EncryptedPassword = "c.muster#2019",
    61. IsInitialPassword = true,
    62. IsActive = true,
    63. Role = LogInDataRoleEnum.Seller
    64. },
    65. Articles = new List<Article>()
    66. {
    67. new Article()
    68. {
    69. ArticleNumber = 123,
    70. Description = "Spiel - Schnappt Hubi",
    71. Price = 2.5m,
    72. Quantity = 1,
    73. Sold = false
    74. }
    75. }
    76. }
    77. }
    78. }
    79. }
    80. }
    81. }
    82. }
    83. }
    84. };
    85. if (!Organizations.Any()) Organizations.Add(organization);
    86. }


    Ist jetzt so erstellt, das seeding, wie es auch im wahren Leben ablaufen würde.
    die Models :
    Organization

    C#-Quellcode

    1. public class Organization : ModelBase, ILogicalDelete, ILogicalTimestamp,IProtocolable
    2. {
    3. [Required(AllowEmptyStrings = false, ErrorMessage = "Bitte geben sie einen Namen an")]
    4. [MinLength(3)]
    5. [MaxLength(150)]
    6. public virtual string Name { get; set; }
    7. public virtual string Street { get; set; }
    8. public virtual string PostalCode { get; set; }
    9. public virtual string City { get; set; }
    10. public virtual string UID { get; set; }
    11. public virtual LicenseTypeEnum License { get; set; }
    12. public virtual ICollection<Person> OrganizationPersons { get; set; }
    13. public virtual ICollection<PersonEvent> OrganizationEvents { get; set; }
    14. public bool DeletedFlag { get; set; }
    15. public DateTime? DeletedTimestamp { get; set; }
    16. public DateTime? LastUpdateTimestamp { get; set; }
    17. public DateTime? CreationTimestamp { get; set; } = DateTime.Now;
    18. public ICollection<Protocol> Protocol { get; set; }
    19. }


    Person

    C#-Quellcode

    1. public class Person : ModelBase, ILogicalDelete, ILogicalTimestamp, IProtocolable
    2. {
    3. [Required(AllowEmptyStrings = false, ErrorMessage = "Bitte geben sie einen Namen an")]
    4. [MinLength(3)]
    5. [MaxLength(150)]
    6. public virtual string FirstName { get; set; }
    7. [Required(AllowEmptyStrings = false, ErrorMessage = "Bitte geben sie einen Namen an")]
    8. [MinLength(3)]
    9. [MaxLength(150)]
    10. public virtual string LastName { get; set; }
    11. [Required(AllowEmptyStrings = false, ErrorMessage = "Bitte geben Sie eine Email Adresse ein")]
    12. public virtual string Email { get; set; }
    13. public virtual string Phone { get; set; }
    14. public virtual LogInData AccessData { get; set; }
    15. public virtual int? SellerNumber { get; set; }
    16. public virtual PersonCategoryEnum PersonCategory { get; set; }
    17. public virtual int OrganizationId { get; set; }
    18. public virtual Organization Organization { get; set; }
    19. public virtual ICollection<Article> Articles { get; set; }
    20. public virtual ICollection<PersonEvent> PersonEvents { get; set; }
    21. public bool DeletedFlag { get; set; }
    22. public DateTime? DeletedTimestamp { get; set; }
    23. public DateTime? LastUpdateTimestamp { get; set; }
    24. public DateTime? CreationTimestamp { get; set; } = DateTime.Now;
    25. public ICollection<Protocol> Protocol { get; set; }
    26. }
    27. #region PersonModel Enums
    28. public enum PersonCategoryEnum
    29. {
    30. OrganizationAdmin = 1,
    31. EventResponsible = 2,
    32. EventSeller = 3,
    33. EventResponsibleSeller = 5
    34. }
    35. public enum LicenseTypeEnum
    36. {
    37. Free = 5, //MaxSellerNumber = 5
    38. Light = 50, //MaxSellerNumber = 50
    39. Pro = 250, //MaxSellerNumber = 250
    40. Enterprise = 500, //MaxSellerNumber = 500
    41. Unlimited = int.MaxValue //MaxSellerNumber = int.MaxValue
    42. }
    43. #endregion


    PersonEvent

    C#-Quellcode

    1. public class PersonEvent : ModelBase, ILogicalDelete, ILogicalTimestamp,IProtocolable
    2. {
    3. public virtual int PersonId { get; set; }
    4. public virtual Person Person { get; set; }
    5. public virtual int BazaarEventId { get; set; }
    6. public virtual BazaarEvent BazaarEvent { get; set; }
    7. public bool DeletedFlag { get; set; }
    8. public DateTime? DeletedTimestamp { get; set; }
    9. public DateTime? LastUpdateTimestamp { get; set; }
    10. public DateTime? CreationTimestamp { get; set; } = DateTime.Now;
    11. public ICollection<Protocol> Protocol { get; set; }
    12. }


    BazaarEvent

    C#-Quellcode

    1. public class BazaarEvent : ModelBase, ILogicalDelete, ILogicalTimestamp, IProtocolable
    2. {
    3. [Required(AllowEmptyStrings = false, ErrorMessage = "Die Veranstaltung muss benannt werden.")]
    4. public virtual string EventName { get; set; }
    5. [Required(ErrorMessage = "Sie müssen das Veranstaltungsdatum angeben.")]
    6. public DateTime EventDate { get; set; }
    7. //kann und darf leer sein (eventuell über Settings bool Variable auf Required setzen?
    8. public virtual double Provision { get; set; }
    9. //kann und darf leer sein (eventuell über Settings bool Variable auf Required setzen?
    10. public virtual decimal MinProvision { get; set; }
    11. //wird in der Datenbank gesetzt anhand der Lizenz (siehe LicenseTypeEnum)
    12. public virtual int MaxSellerNumber { get; set; }
    13. public virtual int OrganizationID { get; set; }
    14. public virtual Organization Organization { get; set; }
    15. public virtual int ResponsiblePersonID { get; set; }
    16. public virtual Person ResponsiblePerson { get; set; }
    17. public virtual ICollection<PersonEvent> Sellers { get; set; }
    18. public bool DeletedFlag { get; set; }
    19. public DateTime? DeletedTimestamp { get; set; }
    20. public DateTime? LastUpdateTimestamp { get; set; }
    21. public DateTime? CreationTimestamp { get; set; } = DateTime.Now;
    22. public ICollection<Protocol> Protocol { get; set; }
    23. }


    Article

    C#-Quellcode

    1. public class Article : ModelBase, ILogicalDelete, ILogicalTimestamp, IProtocolable
    2. {
    3. //soll automatisch vergeben werden (Format: Sellernumber+fortlaufende Nummer)
    4. //[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    5. public virtual int ArticleNumber { get; set; }
    6. [Required(AllowEmptyStrings = false, ErrorMessage = "Artikelbezeichnung muss angegeben werden!")]
    7. [MinLength(3, ErrorMessage = "Geben Sie mindestens 3 Zeichen ein")]
    8. [MaxLength(100, ErrorMessage = "Es Artikelbeschreibung darf höchstens 100 Zeichen beinhalten")]
    9. public virtual string Description { get; set; }
    10. [Required(ErrorMessage = "Preis muss angegeben werden!")]
    11. public virtual decimal Price { get; set; }
    12. public virtual string Size { get; set; }
    13. public virtual int Quantity { get; set; } = 1;
    14. public virtual bool Sold { get; set; }
    15. public virtual int PersonID { get; set; }
    16. public virtual Person Person { get; set; }
    17. public bool DeletedFlag { get; set; }
    18. public DateTime? DeletedTimestamp { get; set; }
    19. public DateTime? LastUpdateTimestamp { get; set; }
    20. public DateTime? CreationTimestamp { get; set; } = DateTime.Now;
    21. public ICollection<Protocol> Protocol { get; set; }
    22. }


    die LogInData spar ich mir, die hat ja keine Verweise.
    Problem beim Seed ist, das er mir den FK_Events_Persons_ResponsibleId anmeckert.
    Also irgendwie passt die Relation zwischen den Models nicht, finde aber den Fehler nicht. Nach meiner Logik müsste es so passen.

    EDIT:
    die genaue Fehlermeldung lautet:
    System.Data.SqlClient.SqlException
    HResult=0x80131904
    Nachricht = Introducing FOREIGN KEY constraint 'FK_Events_Persons_ResponsiblePersonID' on table 'Events' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
    Could not create constraint or index. See previous errors.
    Quelle = Core .Net SqlClient Data Provider
    Stapelüberwachung:
    at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
    at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
    at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
    at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite)
    at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite, String methodName)
    at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
    at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.Execute(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary`2 parameterValues)
    at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues)
    at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IEnumerable`1 migrationCommands, IRelationalConnection connection)
    at Microsoft.EntityFrameworkCore.Storage.RelationalDatabaseCreator.EnsureCreated()
    at de.mhoapps.BazaarManagement.Context.BazaarManagementDbContext..ctor(String connectionString) in C:\Users\conta\source\repos\BazaarManagement\Context\BazaarManagement.Context\BazaarManagementDbContext.cs:line 32
    at de.mhoapps.BazaarManagement.Console.Program.Seed() in C:\Users\conta\source\repos\BazaarManagement\View\BazaarManagement.Console\Program.cs:line 24
    at de.mhoapps.BazaarManagement.Console.Program.Main(String[] args) in C:\Users\conta\source\repos\BazaarManagement\View\BazaarManagement.Console\Program.cs:line 17


    "Hier könnte Ihre Werbung stehen..."

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

    Hallo

    Ich habe das Model ein wenig abgeändert und die Konfiguration der Fluent API vorgenommen damit das Seeding funktioniert.
    Beispielsweise MUSS für eine m:n Beziehung die Fluent API verwendet werden. So intelligent ist EF dann auch nicht das es diese Beziehung selbst anhand der Namenskonvention erkennt.

    Models


    C#-Quellcode

    1. public class Person : ModelBase, ILogicalDelete, ILogicalTimestamp, IProtocolable
    2. {
    3. [Required(AllowEmptyStrings = false, ErrorMessage = "Bitte geben sie einen Namen an")]
    4. [MinLength(3)]
    5. [MaxLength(150)]
    6. public virtual string FirstName { get; set; }
    7. [Required(AllowEmptyStrings = false, ErrorMessage = "Bitte geben sie einen Namen an")]
    8. [MinLength(3)]
    9. [MaxLength(150)]
    10. public virtual string LastName { get; set; }
    11. [Required(AllowEmptyStrings = false, ErrorMessage = "Bitte geben Sie eine Email Adresse ein")]
    12. public virtual string Email { get; set; }
    13. public virtual string Phone { get; set; }
    14. public virtual LogInData AccessData { get; set; }
    15. public virtual int? SellerNumber { get; set; }
    16. public virtual PersonCategoryEnum PersonCategory { get; set; }
    17. public virtual int? OrganizationId { get; set; }
    18. public virtual Organization Organization { get; set; }
    19. public virtual ICollection<Article> Articles { get; set; }
    20. public virtual ICollection<PersonBazaarEvent> PersonEvents { get; set; }
    21. public bool DeletedFlag { get; set; }
    22. public DateTime? DeletedTimestamp { get; set; }
    23. public DateTime? LastUpdateTimestamp { get; set; }
    24. public DateTime? CreationTimestamp { get; set; } = DateTime.Now;
    25. public ICollection<Protocol> Protocol { get; set; }
    26. }
    27. #region PersonModel Enums
    28. public enum PersonCategoryEnum
    29. {
    30. OrganizationAdmin = 1,
    31. EventResponsible = 2,
    32. EventSeller = 3,
    33. EventResponsibleSeller = 5
    34. }
    35. public enum LicenseTypeEnum
    36. {
    37. Free = 5, //MaxSellerNumber = 5
    38. Light = 50, //MaxSellerNumber = 50
    39. Pro = 250, //MaxSellerNumber = 250
    40. Enterprise = 500, //MaxSellerNumber = 500
    41. Unlimited = int.MaxValue //MaxSellerNumber = int.MaxValue
    42. }
    43. #endregion
    44. }


    C#-Quellcode

    1. public class BazaarEvent : ModelBase, ILogicalDelete, ILogicalTimestamp, IProtocolable
    2. {
    3. [Required(AllowEmptyStrings = false, ErrorMessage = "Die Veranstaltung muss benannt werden.")]
    4. public virtual string EventName { get; set; }
    5. [Required(ErrorMessage = "Sie müssen das Veranstaltungsdatum angeben.")]
    6. public DateTime EventDate { get; set; }
    7. //kann und darf leer sein (eventuell über Settings bool Variable auf Required setzen?
    8. public virtual double Provision { get; set; }
    9. //kann und darf leer sein (eventuell über Settings bool Variable auf Required setzen?
    10. public virtual decimal MinProvision { get; set; }
    11. //wird in der Datenbank gesetzt anhand der Lizenz (siehe LicenseTypeEnum)
    12. public virtual int MaxSellerNumber { get; set; }
    13. public virtual int? OrganizationID { get; set; }
    14. public virtual Organization Organization { get; set; }
    15. public virtual int? ResponsiblePersonID { get; set; }
    16. public virtual Person ResponsiblePerson { get; set; }
    17. public virtual ICollection<PersonBazaarEvent> Sellers { get; set; }
    18. public bool DeletedFlag { get; set; }
    19. public DateTime? DeletedTimestamp { get; set; }
    20. public DateTime? LastUpdateTimestamp { get; set; }
    21. public DateTime? CreationTimestamp { get; set; } = DateTime.Now;
    22. public ICollection<Protocol> Protocol { get; set; }
    23. }



    C#-Quellcode

    1. public class PersonBazaarEvent
    2. {
    3. public virtual int? PersonId { get; set; }
    4. public virtual Person Person { get; set; }
    5. public virtual int? BazaarEventId { get; set; }
    6. public virtual BazaarEvent BazaarEvent { get; set; }
    7. }





    PersonBazaarEvent muss weder von ModelBase erben noch die Interfaces implementieren. Hier benötigt es die ganzen Eigenschaften ja nicht.

    Per Fluent API muss diese Zwischentabelle nun noch konfiguriert werden:

    C#-Quellcode

    1. protected override void OnModelCreating(ModelBuilder modelBuilder)
    2. {
    3. modelBuilder.Entity<PersonBazaarEvent>().HasKey(k => new { k.BazaarEventId, k.PersonId });
    4. modelBuilder.Entity<PersonBazaarEvent>().HasOne(a => a.Person).WithMany(b => b.PersonEvents).HasForeignKey(c => c.PersonId);
    5. modelBuilder.Entity<PersonBazaarEvent>().HasOne(a => a.BazaarEvent).WithMany(b => b.Sellers).HasForeignKey(c => c.BazaarEventId);
    6. }


    Und die Seeding Methode habe ich auch etwas umgeschrieben. Mir war das unter C# einfach etwas zu unübersichtlich:

    Seed

    C#-Quellcode

    1. public void Seed()
    2. {
    3. using (var context = new BazaarManagementDbContext(@"Server=(localdb)\MSSQLLocalDB;Database=BazaarDb;Trusted_Connection=True;"))
    4. {
    5. if (!context.Organizations.Any())
    6. {
    7. List<Person> testPersons = new List<Person>();
    8. testPersons.Add(new Person()
    9. {
    10. FirstName = "Organisation",
    11. LastName = "Admin",
    12. Email = "admin@organisation.de",
    13. PersonCategory = PersonCategoryEnum.OrganizationAdmin,
    14. AccessData = new LogInData()
    15. {
    16. UserName = "admin",
    17. EncryptedPassword = GetMD5Hash("admin#2019"),
    18. IsInitialPassword = true,
    19. IsActive = true,
    20. Role = LogInDataRoleEnum.OrganisationAdmin
    21. }
    22. });
    23. testPersons.Add(new Person()
    24. {
    25. FirstName = "Verantwortlicher",
    26. LastName = "Basar",
    27. Email = "verantwortlicher@basar.de",
    28. PersonCategory = PersonCategoryEnum.EventResponsible,
    29. AccessData = new LogInData()
    30. {
    31. UserName = "responsible",
    32. EncryptedPassword = GetMD5Hash("res.ponsible#2019"),
    33. IsInitialPassword = true,
    34. Role = LogInDataRoleEnum.EventAdmin,
    35. IsActive = true
    36. }
    37. });
    38. testPersons.Add(new Person()
    39. {
    40. FirstName = "Claudia",
    41. LastName = "Muster",
    42. Email = "claudia@muster.de",
    43. PersonCategory = PersonCategoryEnum.EventSeller,
    44. SellerNumber = 1,
    45. AccessData = new LogInData()
    46. {
    47. UserName = "cmuster",
    48. EncryptedPassword = "c.muster#2019",
    49. IsInitialPassword = true,
    50. IsActive = true,
    51. Role = LogInDataRoleEnum.Seller
    52. },
    53. Articles = new List<Article>()
    54. {
    55. new Article()
    56. {
    57. ArticleNumber = 123,
    58. Description = "Spiel - Schnappt Hubi",
    59. Price = 2.5m,
    60. Quantity = 1,
    61. Sold = false
    62. }
    63. }
    64. });
    65. context.Persons.AddRange(testPersons);
    66. Organization newOrg = new Organization()
    67. {
    68. Name = "KiTa Kunterbunt e.V.",
    69. License = LicenseTypeEnum.Light,
    70. OrganizationPersons = new List<Person>()
    71. };
    72. BazaarEvent firstEvent = new BazaarEvent()
    73. {
    74. EventName = "Kleider Basar Musterhausen",
    75. EventDate = DateTime.Now.AddDays(14),
    76. Provision = 20,
    77. MinProvision = 5m,
    78. Sellers = new List<PersonBazaarEvent>()
    79. };
    80. firstEvent.Sellers.Add(new PersonBazaarEvent() { BazaarEvent = firstEvent, Person = context.Persons.Local.Last()});
    81. firstEvent.Organization = newOrg;
    82. firstEvent.ResponsiblePerson = context.Persons.Local.ToList()[1];
    83. context.Events.Add(firstEvent);
    84. newOrg.OrganizationPersons.Add(context.Persons.Local.First());
    85. context.Organizations.Add(newOrg);
    86. if (context.SaveChanges() < 1) throw new Exception("Unerwarteter Rückgabewert beim spiechern...");
    87. }
    88. }
    89. }




    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Ich hoffe ihr beiden nehmt es mir nicht böse wenn ich mich hier ab und an mit Fragen einmisch die nocht direkt das Thema betreffen.

    Nofear23m schrieb:

    public virtual int? ResponsiblePersonID { get; set; }


    was genau hat das ? für auswirkungen?

    und folgendes verstehe ich nicht so richtig.
    Kann mir das jemand geauer erklären?

    C#-Quellcode

    1. modelBuilder.Entity<PersonBazaarEvent>().HasKey(k => new { k.BazaarEventId, k.PersonId });
    2. modelBuilder.Entity<PersonBazaarEvent>().HasOne(a => a.Person).WithMany(b => b.PersonEvents).HasForeignKey(c => c.PersonId);
    3. modelBuilder.Entity<PersonBazaarEvent>().HasOne(a => a.BazaarEvent).WithMany(b => b.Sellers).HasForeignKey(c => c.BazaarEventId);

    Das damit eine "Zwischenspalte erstellt wird steht ja im Beitrag über mir. Aber was genau dort passiert verstehe ich nicht.
    Ich versuche mir das gerade wieder Bildlich vorzustellen wie die Tabellen dann im Datasetdesigner aussehen würden und was davon dann der ForeignKey ist.
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.
    Hey, ich kann auch mal was sagen. int? ist ein nullable type. Will heißen, es kann eine Ganzzahl oder Nothing sein. Kann man mit allen Wertetypen machen, damit man ggf. auch sagen kann: Nein, Du hast nicht den Zahlenwert Null (0), sondern Du bist gar nix.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    @Akanel
    klar darfst Du dich einmischen, dadurch lerne ich ja auch...
    das ? Bedeutet, das int auch null sein kann, also nicht 0 sondern null. Nennt sich nullable.
    @Nofear23m danke dir für deine Mühe, ich schaue mir gleich zu Hause die solution an.
    Zum nullable hab ich auch ne Frage, du hattest doch geschrieben das hier an der stelle die nullable keinen sinn machen, weil ja die responsible und events immer gefüllt seien. Bei der organisation kann ich es nachvollzihen, weil das vorkommen könnte

    @Akanel die PersonBazaarEvent ist eine eigene Tabelle und verbindet Person und BazaarEvent weil zwischen den beiden Tabellen eine m:n Beziehung besteht, benötigt man hier eine Zwischenzabelle
    "Hier könnte Ihre Werbung stehen..."

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

    Hallo Leute

    @Akanel Warum denn? Frag ruig, kein Thema. Gerade EF Core ist ein Werkzeug welches immer mehr kommt da es sehr mächtig ist und einem sehr viel arbeit sparen kann.

    Unter VB.Net ist ein ? gleichzusetzen mit Nullable. Wie je @VaporiZed schon richtig erklärt hat.

    @MichaHo
    Ja, das ist richtig. Und im ViewModel wirst du das dann auch nicht zulassen. Aber EF Core benötigt dies. EF weis nicht das du nur als gelöscht markierst wirst und möchte eine Strategie für das CascadeDelete haben. Also für das löschen von zusammenhängenden Datensätzen. EF will nun hergehen und im Falle einer Löschung aber die verknüpften Datensätze doch lieber nicht mitlöschen sondern die Verknüpfte ID auf null setzen. Das geht aber nur wenn diese nullable ist.


    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Jetzt vertauscht du was. Das hat mit dem Interface nix zu tun.
    Google mal CascadeDelete.

    Grüße
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Ja, ich meinte, das vorher ja noch serialisiert werden sollte und das dann da kein nullable dabei sein sollte.
    Nun hab ich ja efCore drann und deine Interfaces.... so meinte ich das.
    Cascade delete kenn ich, bedeutet, das wenn ein datensatz gelöscht wird, das dann alle dazugehörigen datensätze mit gelöscht werden.
    "Hier könnte Ihre Werbung stehen..."
    Achso, so hast du das gemeint. Gut.
    Passt das Modell denn nur so??
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##