0

I have a Database-first approach with a single Table A which tries to simulate a linked list. With the following columns:

- Id (INT)
- Name (NVARCHAR)
- Position (INT)
- NextId (INT)
- PreviousId(INT)

Where NextId and PreviousId are foreign keys to Table A (Id) field.

I am trying to insert a new entity between two existing entities.

var first = dbContext.Tasks.FirstOrDefault(x=>x.Position == 1);
var second = dbContext.Tasks.FirstOrDefault(x=>x.Position == 2);

var newTask = new Tasks{ Name = "My new task", Position = 2, PreviousId = first.Id, NextId = second.Id};

first.NextIdNavigation = newTask;
second.PreviousIdNavigation = newTask;

second.Position = 2;
dbContext.SaveChanges()

This is updating second entity position but not inserting the newTask into the Database, why not?

E. Williams
  • 405
  • 1
  • 6
  • 21
  • https://stackoverflow.com/questions/11670212/entity-framework-wont-detect-changes-of-navigation-properties/11670457 might be relevant – Progman Feb 15 '22 at 21:51
  • I don't find it useful for my issue, since I am using LazyLoading – E. Williams Feb 15 '22 at 22:04
  • Please [edit] your question to include a [mcve], which can be compiled and tested by others. Also add the `CREATE TABLE` statement to your question. – Progman Feb 15 '22 at 22:10
  • Because you did not add it to the Tasks DbContext. – funatparties Feb 15 '22 at 22:39
  • @funatparties Depending on the code/model that might not be required. – Progman Feb 15 '22 at 22:42
  • Maybe `dbContext.Entry(newTask).State = Added` should do the trick? – E. Williams Feb 15 '22 at 22:58
  • @E.Williams `dbContext.Tasks.Add(newTask);` works as well, but it should work already without it, so there might be some issue with your code/model. – Progman Feb 15 '22 at 23:07
  • Are you using lazy loading with or without proxies? – Steve Py Feb 15 '22 at 23:57
  • with proxies @StevePy – E. Williams Feb 16 '22 at 07:37
  • Hmm, with Proxies, instead of newing up a concrete instance, possibly try `var newTask = Context.Tasks.Create();` (or `CreateProxy()` depending on version) initializing the values, and set both ends of the bi-directional references. (set newTask.PreviousIdNavigation & newTask.NextIdNavigation as well as the existing entities) Otherwise it does look like what you have should work, assuming you meant `second.Position = 3;` – Steve Py Feb 16 '22 at 09:40

1 Answers1

0

Let's assume that you configured the Task model into database context the right way. As a side note, I would give it another name.

    public class Task
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Position { get; set; }
        public int? NextId { get; set; }
        public int?  PreviousId { get; set; }

        public Task Next { get; set; }
        public Task Previous { get; set; }
    }

    public class Context : DbContext
    {
        private const string DefaultConnectionString = "Data Source=(localdb)\\MSSQLLocalDB; Initial Catalog = LinkedList";

        public DbSet<Task> Tasks { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseSqlServer(DefaultConnectionString);
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Task>().ToTable("Tasks");

            modelBuilder.Entity<Task>()
                .HasOne(t => t.Next)
                .WithOne(t => t.Previous)
                .HasForeignKey(typeof(Task), "NextId")
                .OnDelete(DeleteBehavior.NoAction);

            modelBuilder.Entity<Task>()
             .HasOne(t => t.Previous)
             .WithOne(t => t.Next)
             .HasForeignKey(typeof(Task), "PreviousId")
             .OnDelete(DeleteBehavior.NoAction);
        }
    }

Moving on, you could do something like this

var first = context.Tasks.FirstOrDefault(t => t.Position == 1);
var second = context.Tasks.FirstOrDefault(t => t.Position == 2);

if (first is not null && second is not null)
{
   var newTask = new Task()
   {
      Name = "Third Task ",
      Position = 3,
      NextId = second.Id,
      PreviousId = first.Id
   };
   
   context.Tasks.Add(newTask);
   context.SaveChanges();
  
   first.Next = newTask;
   newTask.Next = second;

   context.SaveChanges();
}

After creating newTask it should be marked as Added. This can be achieved either by directly modifying its state or by placing it on the contexts DbSet. Next, you should commit the transaction (it is important to avoid a circular reference) and then re-establish the pointed entries for the Next and Previous navigation properties. After that, you can commit the transaction again.

This should be the final result

enter image description here

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
HSBogdan
  • 200
  • 10
  • Thanks, I had already managed to do it by calling `SaveChanges()` two times, but I wanted to avoid it by trying to add the new task throught navigation properties of already existing entities – E. Williams Feb 16 '22 at 07:40