EF one-to-many to assure the link with the main table.

sblb 1,166 Reputation points
2022-11-12T15:27:52.21+00:00

Hi,
I would like to do a link between main table Developer & a table ActionList.

The ActionList take place in edit mode of my app to the address : "/developer/edit/{developerId:int}"

I try to use EF one-to-many to do the link between two tables

I structured the model class has below :

public partial class Developer  
     {  
         public int Id { get; set; }  
          public ICollection<ActionsList>? ActionsLists { get; set; }  
  }  
      
  public class ActionsList  
     {  
         [Key]     
         public int ActionId { get; set; }              
           
         public DateTime ProductionDate { get; set; }  
              
         public int DeveloperId { get; set; }  
         public Developer Developer { get; set; }  
      
     }  

application data :

 public class ApplicationDBContext : DbContext  
     {  
         public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options) : base(options)  
         {  
         }  
         public DbSet<Developer> Developers { get; set; }  
         public DbSet<ActionsList> ActionsLists { get; set; }  
                     
          
         protected override void OnModelCreating(ModelBuilder modelBuilder)  
         {  
             modelBuilder.Entity<ActionsList>()  
                         .HasOne<Developer>(s => s.Developer)  
                         .WithMany(g => g.ActionsLists)  
                         .HasForeignKey(s => s.DeveloperId);         
         }     
          
       }  

when I want to add the actions in page https://localhost:44354/developer/edit/1 this means all actions for developerId = 1 ; any value has posted in ActionList.

But when I've put the break point await http.PostAsJsonAsync("api/actionslist", act); I see that ActionId = null .

I guess that I set up EF one-to-many wrong!

Can you help me how to link the two tables? (This is the first time I've done this)

Thanks in advance

Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,395 questions
{count} votes

Accepted answer
  1. AgaveJoe 26,136 Reputation points
    2022-11-18T12:45:54.757+00:00

    Against my better judgement, I created an example. This is an example NOT a solution. In production code you would NOT pass entities between the client and Web API! Typically you would designed a data interrace and pass poco classes.

    I used followed the following documentation which I have provided many times over many of your threads.

    Tutorial: Create a web API with ASP.NET Core
    Saving Related Data

    Entities

        public partial class Developer  
        {  
            public int DeveloperId { get; set; }  
            public List<ActionItem>? ActionItems { get; set; }  
        }  
      
        public class ActionItem  
        {  
            public int ActionItemId { get; set; }  
            public string? Tilte { get; set; }  
            public string? Description { get; set; }  
            public string? State { get; set; }  
            public DateTime? OpenDate { get; set; }  
            public DateTime? PlanDate { get; set; }  
            public DateTime? CloseDate { get; set; }  
      
            public int DeveloperId { get; set; }  
            public Developer? Developer { get; set; }  
          
    

    Related data configuration

            protected override void OnModelCreating(ModelBuilder modelBuilder)  
            {  
                modelBuilder.Entity<ActionItem>()  
                    .HasOne(d => d.Developer)  
                    .WithMany(d => d.ActionItems);  
            }  
    

    Controller

        [Route("api/[controller]")]  
        [ApiController]  
        public class DeveloperActionController : ControllerBase  
        {  
            private readonly WebApiSqliteContext _context;  
            public DeveloperActionController(WebApiSqliteContext context)  
            {  
                _context = context;  
            }  
            // GET: api/<DeveloperActionController>  
            [HttpGet]  
            public IEnumerable<Developer> GetDevelopersWithActions()  
            {  
                return _context.Developers.Include(a => a.ActionItems).ToList();  
            }  
      
            // GET api/<DeveloperActionController>/5  
            [HttpGet("{id}")]  
            public async Task<ActionResult<Developer>> GetDeveloperActions(int id)  
            {  
                Developer? developer = await _context.Developers  
                    .Include(a => a.ActionItems)  
                    .FirstOrDefaultAsync(d => d.DeveloperId == id);  
      
                if (developer == null)  
                {  
                    return NotFound();  
                }  
      
                return Ok(developer);  
            }  
      
            // POST api/<DeveloperActionController>  
            [HttpPost]  
            public async Task<ActionResult<Developer>> PostAsync([FromBody] Developer developer)  
            {  
                _context.Developers.Add(developer);  
                await _context.SaveChangesAsync();  
      
                return CreatedAtAction(nameof(GetDeveloperActions), new { id = developer.DeveloperId }, developer);  
            }  
      
            // PUT api/<DeveloperActionController>/5  
            [HttpPut("{id}")]  
            public async Task<ActionResult<Developer>> Put(int id, [FromBody] ActionItem actionItem)  
            {  
                Developer? developer = await _context.Developers  
                    .Include(a => a.ActionItems)  
                    .FirstOrDefaultAsync(d => d.DeveloperId == id);  
      
                if (developer == null)  
                {  
                    return NotFound();  
                }  
      
                developer.ActionItems.Add(actionItem);  
                await _context.SaveChangesAsync();  
      
                return CreatedAtAction(nameof(GetDeveloperActions), new { id = developer.DeveloperId }, developer);  
      
            }  
      
            // DELETE api/<DeveloperActionController>/5  
            [HttpDelete("{id}")]  
            public async Task<IActionResult> Delete(int id)  
            {  
                Developer? developer = await _context.Developers  
                    .Include(a => a.ActionItems)  
                    .FirstOrDefaultAsync(d => d.DeveloperId == id);  
      
                if (developer == null)  
                {  
                    return NotFound();  
                }  
      
                _context.Remove(developer);  
      
                await _context.SaveChangesAsync();  
      
                return NoContent();  
            }  
        }  
    

    Post data

    {  
      "developerId": 0,  
      "actionItems": [  
        {  
          "actionItemId": 0,  
          "tilte": "Action Title",  
          "description": "Description",  
          "state": "State",  
          "openDate": "2022-11-17",  
          "planDate": "2022-11-17",  
          "closeDate": "2022-11-17",  
          "developerId": 0  
        }  
      ]  
    }  
      
    

    Put data

    {  
      "actionItemId": 0,  
      "tilte": "Action Title 2",  
      "description": "Description 2",  
      "state": "State 2",  
      "openDate": "2022-11-18",  
      "planDate": "2022-11-18",  
      "closeDate": "2022-11-18",  
      "developerId": 1  
    }  
    

    Configuration is needed to return a entity otherwise you'll get an error.

    builder.Services.AddControllers().AddJsonOptions(x =>  
                    x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);  
    
    0 comments No comments

13 additional answers

Sort by: Newest
  1. AgaveJoe 26,136 Reputation points
    2022-11-22T13:50:50.193+00:00

    The Developer class you've shared is a partial class. I'm wondering if there is another partial class that defines a primary key named Id which makes this whole thread is a waste of everyone's time.


  2. sblb 1,166 Reputation points
    2022-11-22T13:34:29.7+00:00

    The foreign key (DeveloperId) is required to relate the ActionItem to the Developer.

    I understand that. In the example above I wanted to show you that I could populate the ActionITem w/o link with Developer.

    Most importantly, there is no indication the Blazor application actually populates the DeveloperId property which you named "Id".

    I renamed it like you ie Id becomes DeveloperId

     public partial class Developer  
        {  
            public int DeveloperId { get; set; }  
            public List<ActionItem>? ActionItems { get; set; }  
      
    }  
      
     public class ActionItem  
        {  
            [Key]  
            public int ActionId { get; set; }         
            public string? Tilte { get; set; }  
            public string? DescriptionA { get; set; }          
            public string? State { get; set; }          
            public DateTime? OpenDate { get; set; }         
            public DateTime? PlanDate { get; set; }         
            public DateTime? CloseDate { get; set; }  
              
          public int DeveloperId { get; set; }  
           public Developer? Developer { get; set; }  
        }   
    

    When I want to use the foreign key so DeveloperId I always have the same problem.

    Can you see where I have the problem?

    0 comments No comments

  3. sblb 1,166 Reputation points
    2022-11-21T19:09:04.29+00:00

    You will find below two controllers and code behind for the ActionItem :

    DeveloperController :

     [HttpGet]  
            public async Task<IActionResult> Get()  
            {  
                var devs = await _context.Developers.ToListAsync();  
                return Ok(devs);  
            }  
      
            [HttpGet("{id}")]  
            public async Task<IActionResult> Get(int id)  
            {  
                var dev = await _context.Developers.FirstOrDefaultAsync(a => a.DeveloperId == id);  
                return Ok(dev);  
            }         
      
            //Take a number ECO  
            [HttpGet("GenearteEcoById/{id}")]  
            public async Task<ActionResult<Developer>> GenearteEcoById(int id)  
            {  
                var developer = await _context.Developers.FindAsync(id);  
                if (developer == null)  
                {  
                    return NotFound();  
                }  
                if (developer.ECOYear == 0 && developer.ECOCount == 0 && developer.ECOSelected==true)  
                {  
                    var values = await GenerateEcoByIdAsync(id);  
                    developer.ECOYear = values.Year;  
                    developer.ECOCount = values.Count;  
                    await _context.SaveChangesAsync();  
                }  
                else if(developer.ECOSelected == false)  
                {  
                    developer.ECOYear = 0;  
                    developer.ECOCount = 0;  
                    await _context.SaveChangesAsync();  
      
                }  
      
                return developer;  
            }  
              
            private async Task<YearCount> GenerateEcoByIdAsync(int id)  
            {  
                if (!await _context.Developers.AnyAsync(d => d.ECOYear == DateTime.Now.Year))  
                {  
                    return new YearCount() { Count = 1, Year = DateTime.Now.Year };  
                }  
      
                int max = await _context.Developers.Where(d => d.ECOYear == DateTime.Now.Year).MaxAsync(d => d.ECOCount) + 1;  
                return new YearCount() { Count = max, Year = DateTime.Now.Year };  
            }  
      
            //Creates a new Developer with the passed developer object data AND take ECR Number  
            [HttpPost]  
            public async Task<IActionResult> Post(Developer developer)  
            {  
                         
                YearCount dev = await IncrementDeveloperIdAsync();  
      
                developer.Year = dev.Year;  
                developer.Count = dev.Count;  
      
                _context.Add(developer);  
                await _context.SaveChangesAsync();  
                return Ok(developer.DeveloperId);  
                 
            }       
      
      
            //Take the number ECR  
            private async Task<YearCount> IncrementDeveloperIdAsync()  
            {  
                if (!await _context.Developers.AnyAsync(d => d.Year == DateTime.Now.Year))  
                {  
                    return new YearCount() { Count = 1, Year = DateTime.Now.Year };  
                }  
      
                int max = await _context.Developers.Where(d => d.Year == DateTime.Now.Year).MaxAsync(d => d.Count) + 1;  
                return new YearCount() { Count = max, Year = DateTime.Now.Year };  
            }  
      
            //Modifies an existing developer record.  
            [HttpPut]  
            public async Task<IActionResult> Put(Developer developer)  
            {  
                _context.Entry(developer).State = EntityState.Modified;  
                await _context.SaveChangesAsync();  
                return NoContent();  
            }  
      
            [HttpDelete("{id}")]  
            public async Task<IActionResult> Delete(int id)  
            {  
                var dev = new Developer { DeveloperId = id };  
                _context.Remove(dev);  
                await _context.SaveChangesAsync();  
                return NoContent();  
            }  
    

    the data is fetch in datagrid view below
    262700-image.png

    So after that I edit the page

    DeveloperActionController.cs

    // GET: api/<DeveloperActionController>  
            [HttpGet]  
            public IEnumerable<Developer> GetDevelopersWithActions()  
            {  
                return _contexts.Developers.Include(a => a.ActionItems).ToList();  
            }  
      
            // GET api/<DeveloperActionController>/5  
            [HttpGet("{id}")]  
            public async Task<ActionResult<Developer>> GetDeveloperActions(int id)  
            {  
                Developer? developer = await _contexts.Developers  
                                                      .Include(a => a.ActionItems)  
                                                      .FirstOrDefaultAsync(d => d.DeveloperId == id);  
      
                if (developer == null)  
                {  
                    return NotFound();  
                }  
      
                return Ok(developer);  
            }  
      
            // POST api/<DeveloperActionController>/AddAction  
            [HttpPost("/api/DeveloperAction/AddActionItem")]  
            public async Task<ActionResult<Developer>> PostActionItem([FromBody] ActionItem actionItem)  
            {  
                _contexts.ActionItems.Add(actionItem);  
                await _contexts.SaveChangesAsync();  
      
                return CreatedAtAction(nameof(GetDeveloperActions), new { id = actionItem.DeveloperId }, actionItem);  
            }  
      
      
            // PUT api/<DeveloperActionController>/5  
            [HttpPut("{id}")]  
            public async Task<ActionResult<Developer>> Put(int id, [FromBody] ActionItem actionItem)  
            {  
                Developer? developer = await _contexts.Developers  
                                                      .Include(a => a.ActionItems)  
                                                      .FirstOrDefaultAsync(d => d.DeveloperId == id);  
      
                if (developer == null)  
                {  
                    return NotFound();  
                }  
      
                developer.ActionItems.Add(actionItem);  
                await _contexts.SaveChangesAsync();  
      
                return CreatedAtAction(nameof(GetDeveloperActions), new { id = developer.DeveloperId }, developer);  
      
            }  
      
            // DELETE api/<DeveloperActionController>/5  
            [HttpDelete("{id}")]  
            public async Task<IActionResult> Delete(int id)  
            {  
                Developer? developer = await _contexts.Developers  
                    .Include(a => a.ActionItems)  
                    .FirstOrDefaultAsync(d => d.DeveloperId == id);  
      
                if (developer == null)  
                {  
                    return NotFound();  
                }  
      
                _contexts.Remove(developer);  
      
                await _contexts.SaveChangesAsync();  
      
                return NoContent();  
            }  
    

    in code behind I initialize and I use post method to populate ActionItem

       protected override async Task OnInitializedAsync()  
        {  
            actionitems = await http.GetFromJsonAsync<ActionItem[]>("api/developeractions");  
      
        }        
      
        async Task CreateActionList()  
        {          
          // await http.PostAsJsonAsync($"api/developer/edit/{developerId}/developeractions", act);  
           await http.PostAsJsonAsync($"api/developeractions/AddActionItem", act);        
           uriHelper.NavigateTo($"/developer/edit/{developerId}");  
        }  
    

    In Edit page I should see the ActionItem data in datagridview but The new row is created with DeveloperController instead of DeveloperActionsController. It's real mystery!!

    262791-image.png


  4. sblb 1,166 Reputation points
    2022-11-21T17:31:46.773+00:00

    I've made the same controller like you.
    Should I delete the controller named DeveloperController.cs?

    I've always actionsitem = null

    262773-image.png

      [Route("api/[controller]")]  
        [ApiController]  
        public class DeveloperActionsController : ControllerBase  
        {  
            private readonly ApplicationDBContext _contexts;  
      
            public DeveloperActionsController(ApplicationDBContext contexts)  
            {  
                this._contexts= contexts;  
            }  
      
            // GET: api/<DeveloperActionController>  
            [HttpGet]  
            public IEnumerable<Developer> GetDevelopersWithActions()  
            {  
                return _contexts.Developers.Include(a => a.ActionItems).ToList();  
            }  
      
            // GET api/<DeveloperActionController>/5  
            [HttpGet("{id}")]  
            public async Task<ActionResult<Developer>> GetDeveloperActions(int id)  
            {  
                Developer? developer = await _contexts.Developers  
                    .Include(a => a.ActionItems)  
                    .FirstOrDefaultAsync(d => d.DeveloperId == id);  
      
                if (developer == null)  
                {  
                    return NotFound();  
                }  
      
                return Ok(developer);  
            }  
      
         // POST api/<DeveloperActionController>/AddAction  
        [HttpPost("/api/DeveloperAction/AddActionItem")]  
        public async Task<ActionResult<Developer>> PostActionItem([FromBody] ActionItem actionItem)  
        {  
            _contexts.ActionItems.Add(actionItem);  
            await _contexts.SaveChangesAsync();  
    
            return CreatedAtAction(nameof(GetDeveloperActions), new { id = actionItem.DeveloperId }, actionItem);  
        }  
      
            // PUT api/<DeveloperActionController>/5  
            [HttpPut("{id}")]  
            public async Task<ActionResult<Developer>> Put(int id, [FromBody] ActionItem actionItem)  
            {  
                Developer? developer = await _contexts.Developers  
                    .Include(a => a.ActionItems)  
                    .FirstOrDefaultAsync(d => d.DeveloperId == id);  
      
                if (developer == null)  
                {  
                    return NotFound();  
                }  
      
                developer.ActionItems.Add(actionItem);  
                await _contexts.SaveChangesAsync();  
      
                return CreatedAtAction(nameof(GetDeveloperActions), new { id = developer.DeveloperId }, developer);  
      
            }  
      
            // DELETE api/<DeveloperActionController>/5  
            [HttpDelete("{id}")]  
            public async Task<IActionResult> Delete(int id)  
            {  
                Developer? developer = await _contexts.Developers  
                    .Include(a => a.ActionItems)  
                    .FirstOrDefaultAsync(d => d.DeveloperId == id);  
      
                if (developer == null)  
                {  
                    return NotFound();  
                }  
      
                _contexts.Remove(developer);  
      
                await _contexts.SaveChangesAsync();  
      
                return NoContent();  
            }