I'm having some issues with Entity Framework on .NET Framework. My app is designed as such:
I have my class that inherits from DbContext and I have this property:
public DbSet<User> Users { get; set; } // ...other properties protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<UserManageableZones>() .HasRequired(umz => umz.User) .WithMany(u => u.ManageableZones) .HasForeignKey(umz => umz.Username) .WillCascadeOnDelete(false); } My User model is something like this:
[Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] [Required] [MaxLength(128)] [Column("USERNAME")] public string Username { get; set; } = string.Empty; public virtual ICollection<UserManageableZones> ManageableZones { get; set; } public User() { ManageableZones = new HashSet<UserManageableZones>(); } As you can see, I have the property ManageableZones, that is the table I want to relate with, with a 1-to-many relationship. So basically one user can have many UserManageableZones (for simplicity's sake, a zone can be related with only 1 user, logically this is incorrect, but I don't care about having a many-to-many relationship).
This is my UserManageableZones:
public virtual User User { get; set; } [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Required] [Column("ID")] public int Id { get; set; } [ForeignKey("User")] [Required] [Column("USERNAME")] public string Username { get; set; } // ...other properties Then, I have a class that is basically a projection of the data from my database:
public Class DataContext { public List<User> Users {get; set;} public DataContext() { Users = new List<User>(); } } DataContext is the "static" object with which I'll interact with data, adding/deleting/updating and so on. Then I'll save this data back to the database.
To do so, I have a DataAccess class, that basically inits the DataContext by populating all its properties with database's data, and updating database data with the one from the DataContext.
To save the data into the database, I have this, IMO, horrible method that unfortunately I didn't really understand how it works, so I didn't really ever touch it
private void SaveTable<T1, T2>(List<T1> entity, bool canUpdate = true, bool canDelete = true) where T1 : class { PrintCallStack(false); try { AMMContext db; if (AMMContext.DbType == Entity.eDbType.SqLite) db = new AMMContext(AMMContext.LoadConnectionString()); else db = new AMMContext(); using (db) { List<T1> listInDB = (List<T1>)Activator.CreateInstance(typeof(List<T1>)); DbSet<T1> dbSet = null; foreach (PropertyInfo pInfo in typeof(AMMContext).GetProperties()) { if (pInfo.GetValue(db) != null) { if (pInfo.GetValue(db).GetType() == typeof(DbSet<T1>)) { dbSet = (DbSet<T1>)pInfo.GetValue(db); listInDB = dbSet.ToList(); } } } List<T2> idEntityList = null; if (entity != null) idEntityList = entity.Select(y => (T2)GetPrimaryKeyValue(y)).ToList(); //List<T1> updating = entity.Where(x => idListinDb.Contains((T2)GetPrimaryKeyValue(x))).ToList(); //List<T1> adding = entity.Where(x => !idListinDb.Contains((T2)GetPrimaryKeyValue(x))).ToList(); if (canUpdate) dbSet.AddOrUpdate(entity.ToArray()); if (canDelete) { List<T1> deleting = dbSet.ToList().Where(x => !idEntityList.Contains((T2)GetPrimaryKeyValue(x))).ToList(); deleting.ForEach(x => dbSet.Attach(x)); dbSet.RemoveRange(deleting); } db.SaveChanges(); } } catch (Exception e) { try { PrintCallStack(true); Log.ErrorFormat($"DataAccess.Save error, transaction rollback occurred | exc:{e.InnerException.InnerException.Message}"); } catch (Exception ex) { Log.ErrorFormat($"DataAccess.Save error, rollback procedure failure"); StringBuilder sb = new StringBuilder(); sb.Append("For entity type : "); if (entity != null) { foreach (T1 item in entity) { sb.Append(item.GetType().Name); sb.Append(" - "); } } Log.ErrorFormat(sb.ToString()); } } } This method is called by the DataAccess all around the code, wrapped inside another method that does something like this
public void Save(bool allFilter = true, bool usersFilter = false, ...other filters) { if (ammUsersFilter | allFilter) SaveTable<User, string>(entity: DataContext.Users, canUpdate: true, canDelete: !initialConditions); // ...same for other filters } That's about it. The problem is that the collection of UserManageableZones inside User, it's not getting saved in its table. As it is right now, when the app starts from scratch, the database is created and also the table USER_ZONES is created.
I've tried to manually create a record inside the USER_ZONES table, then boot up the app and saw that when the data is gathered from the database, I actually have that record inside the user. This makes me think that relation wise, my work is correct. But I have no clue about what I'm missing.
Ah, just to mention. Inside my USER table, I have no column related to USER_ZONES, but I think it's correct. That's it.
These are the script generated of both tables
CREATE TABLE AMM_USER ( USERNAME VARCHAR(128) NOT NULL, ... CONSTRAINT PK_AMM_USER PRIMARY KEY (USERNAME) ); CREATE TABLE USER_ZONES ( ID INTEGER NOT NULL, USERNAME VARCHAR(128) NOT NULL, CONSTRAINT PK_USER_ZONES PRIMARY KEY (ID), CONSTRAINT FK_USER_ZONES_AMM_USER_USERNAME FOREIGN KEY (USERNAME) REFERENCES AMM_USER(USERNAME) ); CREATE INDEX FK_USER_ZONES_AMM_USER_USERNAME ON USER_ZONES (USERNAME); CREATE INDEX IX_USER_ZONES_USERNAME ON USER_ZONES (USERNAME);
SaveChangesis called. It's not a database model or connection - it doesn't have to even open a connection until it has to load or save data. A DbContext is meant to be disposed when the use case/scenario/action ends, to discard unwanted changesSaveTable'sSaveChangescan easily perform 36 DELETES and 47 UPDATES on unrelated "tables" if there are any pending changes. That's a problem ofSaveTable, not SaveChanges.