Entity Framework table not updating database - entity-framework

My application does something strange, if I execute the following code outside the debugger it does not work, but if I run it with the debugger it works fine: keeping in mind that I step through the code, and not continue the next instant.
As I can gather from the debugger, there is nothing wrong with the code, but maybe there is, I at least cannot find it.
public void Reject(string id)
{
using (context)
{
int intId = Convert.ToInt32(id);
Hours hr = (from h in context.Hours
where h.Id == intId
select h).FirstOrDefault();
hr.Status = 30;
context.SaveChanges();
}
}
ApplicationDBContext class
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
static ApplicationDbContext()
{
System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<ApplicationDbContext, Configuration>());
}
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public DbSet<Department> Departments { get; set; }
public DbSet<Tasks> Tasks { get; set; }
public DbSet<Hours> Hours { get; set; }
public DbSet<OffDays> OffDays { get; set; }
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>()
.HasRequired(u => u.Department)
.WithMany(d => d.Users)
.Map(c => c.MapKey("DepartmentId"));
modelBuilder.Entity<ApplicationUser>()
.HasMany(u => u.Supervisors)
.WithMany();
modelBuilder.Entity<Department>()
.HasMany(d => d.Supervisors)
.WithOptional()
.Map(c => c.MapKey("SupervisorId"));
modelBuilder.Entity<Hours>()
.HasRequired(u => u.UserId)
.WithMany(h => h.Hours)
.Map((c => c.MapKey("Hours")));
}
}
Do you guys have an idea what I can try?

It looks like you are reusing the same DbContext instance through the life of your application. This goes against Microsoft's recommended best practices, and may introduce some of the odd behavior you are seeing. DbContext manages a LOT of cached data inside each DbContext instance, and keeping it alive for the entire application can cause all sort of craziness as different cached entities are returned. This caching is what I think is causing your problem. Somewhere in your application this instance is getting disconnected from the context, and now that disconnected, cached instance is being returned the next time you ask for it.
The current Entity Framework best practices recommend that you create a new instance of the context for each logical "unit of work".
The easiest way to create a new context instance on demand is like this:
using (var context = new ApplicationDbContext())
{
//Code here that uses the context
}

You must first attach the entity that you have retrieved
public void Reject(string id)
{
using (context)
{
int intId = Convert.ToInt32(id);
Hours hr = (from h in context.Hours
where h.Id == intId
select h).FirstOrDefault();
context.Attach( hr);// here is difference
hr.Status = 30;
context.SaveChanges();
}
}

Related

EF Core 6 not loading child entities

I am working on a simple web API that is just supposed to parse a JSON tree, and save it to a database. I am working with EF Core 6.0.4 and my application shows a really weird behaviour: right after saving the tree, it loads from the context just fine. But when I call a different endpoint and load the data using a freshly initialized context, the children won't load. I believe it's due to the EF config, but I can't figure out how to load the children.
I tried using _context.Entity(returnValue).Collection(x => x.Children) but the x in the lambda only has ICollection extension methods, not the model fields like in the code examples I saw.
I also tried using .Include, but that seems to be a thing from regular EF.
Here's some of my code:
Controller
public class CategoryTreeManagerController : ControllerBase
{
private readonly CategoryTreeManagerService _service;
public CategoryTreeManagerController(CategoryTreeManagerService service)
{
_service = service;
}
[HttpGet]
public IEnumerable<CategoryTreeNode> GetTree()
{
return _service.GetTree(); //this only returns the root node without any children
}
[HttpPost]
public IEnumerable<CategoryTreeNode> SaveTree(CategoryTreeNode[] nodes)
{
_service.SaveTree(nodes[0]);
return _service.GetTree(); //this correctly returns the tree
}
}
Service
public class CategoryTreeManagerService
{
private readonly CategoryTreeManagerApiDbContext _context;
public CategoryTreeManagerService(CategoryTreeManagerApiDbContext context)
{
_context = context;
}
public IEnumerable<CategoryTreeNode> GetTree()
{
CategoryTreeNode[] returnValue = _context.CategoryTreeNodes
.Where(x => x.Parent == null) //just return the root node
.ToArray(); //easier for frontend
return returnValue;
}
public void SaveTree(CategoryTreeNode node)
{
if (_context.CategoryTreeNodes.Any(x => x.Id == node.Id))
{
_context.Update(node);
}
else
{
_context.CategoryTreeNodes.Add(node);
}
_context.SaveChanges();
}
}
DbContext
public class CategoryTreeManagerApiDbContext : DbContext
{
public CategoryTreeManagerApiDbContext() : base ()
{
Database.EnsureCreated();
}
public CategoryTreeManagerApiDbContext(DbContextOptions<CategoryTreeManagerApiDbContext> options) : base(options)
{
Database.EnsureCreated();
}
public DbSet<CategoryTreeNode> CategoryTreeNodes { get; set; }
public DbSet<TreeNodeDetail> TreeNodeDetails { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CategoryTreeNode>(entity =>
{
entity.HasKey(x => x.Id);
entity.HasOne(x => x.Parent)
.WithMany(x => x.Children)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
});
}
Model classes
public class CategoryTreeNode
{
public int Id { get; set; }
public string Label { get; set; }
public string Story { get; set; }
public string Icon { get; set; }
public ICollection<TreeNodeDetail> Details { get; set; }
public ICollection<CategoryTreeNode> Children { get; set; }
[JsonIgnore]
public CategoryTreeNode? Parent { get; set; }
}
public class TreeNodeDetail
{
[JsonPropertyName("detailId")]
public string Id { get; set; }
[JsonPropertyName("detailTitle")]
public string Title { get; set; }
[JsonPropertyName("detailValue")]
public string Value { get; set; }
[JsonIgnore]
[ForeignKey("CategoryTreeNode")]
public int CategoryTreeNodeId { get; set; }
}
Include would work for one level, or more if you expand out the expression, but it's not an ideal solution for tree structures which could be variable depth. (I.e. child of a child of a child ...)
For instance:
CategoryTreeNode[] returnValue = _context.CategoryTreeNodes
.Include(x => x.Children)
.Where(x => x.Parent == null) //just return the root node
.ToArray(); //easier for frontend
would load all parents and their first level children. To load 2nd level children:
CategoryTreeNode[] returnValue = _context.CategoryTreeNodes
.Include(x => x.Children)
.ThenInclude(x => x.Children)
.Where(x => x.Parent == null) //just return the root node
.ToArray(); //easier for frontend
The issue is knowing how many levels to load, and each level produces a Cartesian Product for EF to work through, exponentially increasing the amount of data being loaded to build a tree. Loading an entire table once quickly becomes a much more efficient solution.
If you have a Single tree structure where you expect only one top-level entry, or want to load an entire reasonable set of top-level nodes then loading all entries into memory will work since EF will be tracking all of the entities and it can resolve all of the various references as it builds the entity structure. This has to load the entire set even if you only want one specific parent out of several possible parents.
If you have several top level parent nodes and a sizeable overall table size to work through, and do want to be able to load a single parent and it's children then one option is to add a de-normalized top-level ID reference to the tree node.
public int Id { get; set; }
public int? TopLevelId { get; set; }
This would be a null-able FK but does not need a navigation property. The current Parent reference would continue to use a shadow property. (I.e. ParentId) In this way once you have an ID for the top-level parent you want to load a tree for:
_context.CategoryTreeNodes.Where(x => x.TopLevelId == topLevelId).ToList();
var topLevelNode = _context.CategoryTreeNodes.Single(x => x.Id == topLevelId);
The first statement will have the DBContext load and track all nodes under that top level tree node. Then when you call the DbContext to get that top level node, the tracked related entities will all get filled in.
The caveat of this approach is that it is a denormalization, in that there is no DB-level assertion that a TopLevelId is set, or remains set correctly. For instance if nodes can be moved between top-level entities and you forget to update this value, this node would not be loaded and associated under the new parent using the above load.

Entity Framework Core (6.0.8) indirect Many to Many relationship with 2 db contexts

I am unable to generate a migration for a many to many relationship that spans 2 different db contexts.
I have 3 entities:
AccountApp, SubscriptionDetail, SubscribedAccountApp
AccountApp and SubscribedAccountApp are the AccountDbContext while the SubscriptionDetail is in a SubscriptionsDbContext.
I want to set up an indirect many to many relationship as described here
Based on the documentation, I have created an EntityTypeConfig for the SubscribedAccountApp like this
public class SubscribedAccountAppConfig : IEntityTypeConfiguration<SubscribedAccountApp>
{
public void Configure(EntityTypeBuilder<SubscribedAccountApp> builder)
{
builder.ToTable("subscribed_account_apps");
builder.HasKey(m => new { m.SubscriptionDetailId, m.AccountAppId });
builder.HasOne(x => x.SubscriptionDetail)
.WithMany(x => x.SubscribedAccountApps)
.HasForeignKey(x => x.SubscriptionDetailId);
builder.HasOne(x => x.AccountApp)
.WithMany(x => x.SubscribedAccountApps)
.HasForeignKey(x => x.AccountAppId);
}
}
This config is then applied to the AccountDbContext like so
public class AccountDbContext : DbContext
{
public IQueryable<AccountApp> AccountApps { get; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new AccountAppConfig());
modelBuilder.ApplyConfiguration(new SubscribedAccountAppConfig());
}
}
The data model for SubsriptionsDetails looks like:
public class SubscriptionDetail
{
private readonly List<SubscribedAccountApp> _subscribedAccountApps;
private readonly List<ConsumptionComponent> _consumptionComponents;
public SubscriptionDetail(
Guid id,
IEnumerable<ConsumptionComponent> consumptionComponents = null,
IEnumerable<SubscribedAccountApp> subscribedAccountApps = null)
{
Id = id;
_consumptionComponents = consumptionComponents ?? new List<ConsumptionComponent>();
_subscribedAccountApps = subscribedAccountApps ?? new List<SubscribedAccountApp>();
}
public Guid Id { get; set; }
public IReadOnlyCollection<ConsumptionComponent> ConsumptionComponents => _consumptionComponents;
public IReadOnlyCollection<SubscribedAccountApp> SubscribedAccountApps => _subscribedAccountApps;
}
The data model for AccountApp looks like:
public class AccountApp
{
public readonly List<SubscribedAccountApp> _subscribedAccountApps;
public AccountApp(Guid id,
Guid accountId,
IEnumerable<SubscribedAccountApp> subscribedAccountApps = null)
{
Id = id;
_subscribedAccountApps = subscribedAccountApps.ToNavigationProperty();
}
public Guid Id { get; private set; }
public IReadOnlyCollection<SubscribedAccountApp> SubscribedAccountApps => _subscribedAccountApps;
}
This leaves me an error when I try to create a migration for the AccountDbContext:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
System.InvalidOperationException: No suitable constructor was found for entity type 'ConsumptionComponent'.
This error describes a ConsumptionComponent constructor for some reason, although this model has already existed and nothing has changed on that model. This model does appear as an IEnumerable in the SubscriptionDetail constructor but I am not sure why it is showing up as an error. All I want this migration to do is add support for the indirect-many-to-many-relationship which has nothing to do with ConsumptionComponent.

EF Core 6 : AutoInclude(false) still loads Navigation

READ THE EDIT!
I have two Entities :
public class Principal {
public Guid Id { get; private set; }
public Collection<Dependant> Dependants { get; init; } = new();
public Principal() { }
}
public class Dependant{
public Guid Id { get; private set; }
public Guid PrincpalId { get; private set; }
public Principal Principal{ get; private set; }
public Dependant() { }
}
I access Principal through a repository :
internal class PrincipalsRepository {
private readonly DbSet<Princpal> db;
public PrincipalsRepository (DbSet<Princpal> db) {
this.db = db;
}
public async Task AddAsync(Principal p) {
await this.db.AddAsync(p).ConfigureAwait(false);
}
public async Task<Principal>> GetByIdAsync(Guid id) {
//Notice how there's no Include here!
return await db
.FirstOrDefaultAsync(p => p.Id == id)
.ConfigureAwait(false);
}
}
I configure them like this :
public void Configure(EntityTypeBuilder<Principal > builder) {
builder
.ToTable("Principals")
.HasKey(p => p.Id);
builder
.Navigation(p => p.Dependants)
.AutoInclude(false); //THIS!!!!!
builder
.OwnsMany(p =>
p.Dependants,
navBuilder => {
navBuilder.ToTable("Dependants");
navBuilder.Property<Guid>("Id"); //Important: without this EF would try to use 'int'
navBuilder.HasKey("Id");
navBuilder
.WithOwner(v => v.Principal)
.HasForeignKey(v => v.PrincipalId);
}
);
}
The repo is used in a DbContext:
//PLEASE NOTE: This code might seem a bit broken to you because it's a trimmed down copy-paste from the real code.
public abstract class MyDatabase<TContext> : DbContext
where TContext : DbContext {
public PrincipalsRepository PrincipalsRepository = new PrincipalsRepository (DbPrincipals);
//This is exposed for unit tests
public DbSet<Principal> DbPrincipals { get; set; }
public MyDatabase(DbContextOptions<TContext> options)
: base(options) {
}
}
I configure an in-memory Db :
//PLEASE NOTE: Not everything is detailed here. It's a copy paste from a bigger code base)
private static Database CreateDatabase() {
var _connection = new SqliteConnection("Filename=:memory:");
_connection.Open();
_contextOptions = new DbContextOptionsBuilder<MyDatabase>()
.UseSqlite(_connection)
.Options;
var context = new MyDatabase(_contextOptions);
return context;
}
I run a unit test where I insert an Principal entity with a Dependant:
// Step 1 : Init
using var context = CreateDatabase();
var repo = new PrincipalsRepository(context.DbPrincipals);
// Step 2 : Insertion
var p = new Principal();
p.Dependants.Add(new Dependant());
await context.PrincipalsRepo.AddAsync(p).ConfigureAwait(false);
await context.SaveChangesAsync().ConfigureAwait(false);
// Step 3 : Read back
var p2 = context.PrincipalsRepo.GetByIdAsync(p.Id).ConfigureAwait(false);
And then...
Assert.Empty(p2!.Dependants); //The unit test fails because I can see that the Dependant has been loaded
What am I doing wrong? Why is it loaded despite me saying "AutoInclude(false)" ?
Note: After adding AutoInclude(false), creating a new migration changed the Db's model snapshot, but the migration itself was empty. Is that normal???
EDIT:
Like #DavidG and #Gert Arnold suggested in the comments, apparently I need to instantiate a brand new DbContext to do the test, because EF is somehow smart enough to pick up that p2 is the "same" as p, and... populates its navigation links (i.e. does the auto Include) without me asking?!?
I absolutely don't understand what's the logic here (in terms of behaviour consistency).
When I change the test and query p2 from a brand new DbContext instance, it works as I would expect it. I.e. it does find the Principal (p2) but its Dependants collection is empty.
Is this documented anywhere, in one form or another? Even as an implicit sentence that seems obvious on some Microsoft help page?

Entity Framework Core 5.0.3 - One To One mapping with shadow property foreign keys

For the life of me, I cannot get this to work properly. I have a relatively simple domain model that has a couple of navigation properties that I want to fill out via eager loading.
To keep my domain model pure, I have opted to use shadow properties as foreign keys, so they are not accessible by the client code.
This is the domain model:
public class CourseType : Entity
{
protected CourseType() { }
public CourseType(string name, CoachGroup coachGroup, bool active)
{
Name = name;
CoachGroup = coachGroup;
Active = active;
}
public string Name { get; private set; }
private int? _coachGroupId;
private int? CoachGroupId => _coachGroupId;
public virtual CoachGroup CoachGroup { get; private set; }
public int? AgeLimit { get; private set; }
public bool Active { get; private set; }
public bool ShowInSearchForm { get; private set; }
private List<Course> _accessGivingCourses = new List<Course>();
public virtual IReadOnlyList<Course> AccessGivingCourses => _accessGivingCourses?.ToList();
}
This is how I wire the configuration up:
public class CourseTypeConfiguration : IEntityTypeConfiguration<CourseType>
{
public void Configure(EntityTypeBuilder<CourseType> builder)
{
builder.ToTable("CourseTypes", "Courses");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).HasColumnName("CourseTypeId");
builder.Property(p => p.Name).HasColumnName("CourseTypeName");
builder.Property(p => p.Active).HasColumnName("IsActive");
builder.Property(p => p.ShowInSearchForm).HasColumnName("ShowInSearchForm");
builder.Property(p => p.AgeLimit).HasColumnName("AgeLimit");
builder.Property<int?>("CoachGroupId").HasField("_coachGroupId");
builder.HasOne(p => p.CoachGroup).WithOne().HasForeignKey<CourseType>("CoachGroupId").OnDelete(DeleteBehavior.Restrict);
builder.HasMany(p => p.AccessGivingCourses).WithMany("AccessGivingCourses")
.UsingEntity<Dictionary<string, object>>("CourseTypesAccessGivingCourses",
j => j.HasOne<Course>().WithMany().HasForeignKey("CourseId"),
j => j.HasOne<CourseType>().WithMany().HasForeignKey("CourseTypeId"),
j => j.ToTable("CourseTypesAccessGivingCourses")
);
builder.HasIndex("CoachGroupId").IsUnique(false);
}
}
This is how I extract the data via my repository class:
public override async Task<IEnumerable<CourseType>> GetAll()
{
try
{
return await Context.CourseTypes.Include(i => i.CoachGroup).Include(i => i.AccessGivingCourses).ToListAsync();
}
catch (Exception e)
{
Logger.LogCritical(e, $"Could not retrieve list of course type entities");
throw;
}
}
It ALMOST works, except for the fact that when I add or update entities, the CoachGroup link randomly gets lost for some updates. For others, it works just fine. It's like Entity Framework Core OR the database randomly loses track of it. Which is odd, because when I look in the database table, the foreign keys in the table are all there like they're supposed to!?
Does anyone have any idea what the hell I am doing wrong? Or if this is the correct approach to this problem at all? All I want to do is to load related data, but it's getting to the point where it's becoming rocket science to merely link a couple of optional relationships together...

why isn't eager loading working in Entity Framework

I'm trying to do basic eager loading on a list of ProjectVersions where each ProjectVersion has a list of FieldValues and ChildProjects. I want the FieldValues and ChildProjects to be loaded along with all their properties when ProjectVersions is loaded, but it seems that in my code when going through each ProjectVersion, it still hits the database to get these collections (checking sql server profiler). Any pointers would be helpful.
var publishedList = Repository.Find<Project>().//a bunch of wheres and selects
IEnumerable<ProjectVersion> publishedList = published
.Include(x => x.FieldValues)
.Include(x => x.ChildProjects)
.ToList();
//EDIT: the context is hidden behind a generic Repository. Below are some details:
public class Repository : IRepository
{
internal readonly IDataContext _context;
public Repository(IDataContext context)
{
_context = context;
_context.Committed += _context_Committed;
_context.Deleted += _context_Deleted;
}
public IQueryable<T> Find<T>() where T : class, IEntity
{
return _context.Repository<T>();
}
}
public class EfDataContext : IDataContext
{
public IQueryable<T> Repository<T>() where T : class, IEntity
{
var table = _context.Set(typeof(T));
WrappedFieldsObjectQuery<T>(table.Cast<T>().AsExpandable()));
return table.Cast<T>().AsExpandable();
}
}
public class MsmDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
.Where(type =>
type.IsClass &&
type.BaseType.IsGenericType &&
type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
foreach (var config in typesToRegister.Select(Activator.CreateInstance))
{
modelBuilder.Configurations.Add((dynamic)config);
}
base.OnModelCreating(modelBuilder);
}
}
public class ProjectMapping : EntityTypeConfiguration<Project>
{
public ProjectMapping()
{
HasOptional(p => p.LastChangedBy).WithMany(p => p.ProjectsChanged).WillCascadeOnDelete();
HasRequired(p => p.CreatedBy).WithMany(p => p.ProjectsCreated).WillCascadeOnDelete();
HasRequired(d => d.Account).WithMany(p => p.Projects).WillCascadeOnDelete();
HasRequired(d => d.PinType).WithMany(p => p.Projects).HasForeignKey(p => p.PinType_Id).WillCascadeOnDelete();
}
}
public static class RepositoryFactory
{
public static IRepository CreateRepository()
{
return CreateEfRepository();
}
internal static IRepository CreateEfRepository()
{
return new Repository(new EfDataContext(new MsmDbContext()));
}
}
Ok, I can't see your complete query, but in your comments you wrote something about select. EF will ignore Include() statements as soon as you are doing custom projections using Select(), my guess is that's why eager loading does not work. Instead of Include(), try to add the properties you want to load to your projection, something like
Repository.Find<Project>()
.Select(p => new { project = p, p.FieldValues, p.ChildProjects })
.AsEnumerable().Select(p => p.project).ToList()
This way the projection will take care of loading your data, you don't need Include().
Actually, I got it to work by just directly working with the DataContext. Somehow the Repository is screwing it up.