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,404 questions
{count} votes

Accepted answer
  1. AgaveJoe 26,146 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: Most helpful
  1. sblb 1,166 Reputation points
    2022-11-17T20:20:37.467+00:00

    If you changed your mind and want to add a single ActionItem to a Developer then show this action.

    I don't change ; I want several ACtionItem for one Developer row so the method one-to-many seems appropriate.

    share where you are following the pattern shown in the Saving Related Data

    here related-data

    Class model Developer and ActionItem

     public partial class Developer  
        {  
            public int Id { get; set; }  
            public List<ActionItem>? ActionItems { get; set; }  
        }  
    
     public class ActionItem  
        {  
            [Key]  
            public int ActionId { 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 Id { get; set; }  
            public Developer? Developer { get; set; }  
        }  
    

    The column DeveloperId is created in table ActionItem, see below initial migration

     constraints: table =>  
                    {  
                        table.PrimaryKey("PK_ActionItems", x => x.ActionId);  
                        table.ForeignKey(  
                            name: "FK_ActionItems_Developers_DeveloperId",  
                            column: x => x.DeveloperId,  
                            principalTable: "Developers",  
                            principalColumn: "Id");  
                    });  
    
    0 comments No comments

  2. sblb 1,166 Reputation points
    2022-11-18T18:04:17.707+00:00

    Thanks to your proposition.
    But it's doesn't work.

    I don't obtain e.g Developer row (id=1) with several actions in page edit.

    In your class model you use DeveloperId inn my side I use Id for two class.

    In edit page in @Aidan Wick I call the post method as below :

     async Task CreateActionList()  
        {          
            await http.PostAsJsonAsync("api/developeractions", act);  
            uriHelper.NavigateTo($"/developer/edit/{developerId}");  
        }  
    

    Have you an idea why I didn't populate the ActionITem?

    I have also the 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.Id == 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.Id);  
        }        
         
        //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 { Id = id };  
            _context.Remove(dev);  
            await _context.SaveChangesAsync();  
            return NoContent();  
        }  
    

  3. sblb 1,166 Reputation points
    2022-11-19T18:04:28.74+00:00

    I've made any corection for moment
    You right there is status 400 in dev tools.

    Also I've this message in devtools. I don't understand because I am in edit mode and this field is not required only in create mode

    262108-image.png

    0 comments No comments

  4. sblb 1,166 Reputation points
    2022-11-20T09:15:41.247+00:00

    I have removed the [required] annotations and I no longer have an error.
    However, the ActionItem table is still not filled.

    When I'm in the developer/edit mode I want to add an action; and surprise I add a line in developer. really I don't understand!

    in address : https://localhost:44354/developer/edit/8 I added the button to add the actions.
    When I validate the create button I create a row in developer while I want to create an action for the developer/edit/8

    262203-image.png