ASP.NET Core Web API & EF Core Models with Foreign Key relationship

asp.net asp.net-web-api2 c# entity-framework entity-framework-core

Question

I'm developing a basic Web API project for education purposes, and I am having trouble with my EF Model relationships. I have 2 models. Message and MessageBoard.

public class Message
    {
        public long Id { get; set; }
        public string Text { get; set; }
        public string User { get; set; }
        public DateTime PostedDate { get; set; }

        public long MessageBoardId { get; set; }
        [ForeignKey("MessageBoardId")]
        public MessageBoard MessageBoard { get; set; }
    }

public class MessageBoard
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }

        public ICollection<Message> Messages { get; set; }
    }

I've setup my DBContext and created a migration to configure the database. I generated two Web API controllers using EF Scaffolding. The migration appears to correctly detect the relationship between the two models:

modelBuilder.Entity("Asp.net_Core_Web_Api.Models.Message", b =>
{
   b.HasOne("Asp.net_Core_Web_Api.Models.MessageBoard", "MessageBoard")
   .WithMany("Messages")
   .HasForeignKey("MessageBoardId")
   .OnDelete(DeleteBehavior.Cascade);
});

But, when I create a MessageBoard and then create a Message with the ID of the MessageBoard, they don't appear to link correctly. In PostMan, I am doing the following:

1) Post a new MessageBoard

POST - https://localhost:44384/api/MessageBoards/

Body - Raw - Json

{
 "Name":"Test Board",
 "Description":"A Message board for testing purposes."
}

Returns

{
    "id": 4,
    "name": "Test Board",
    "description": "A Message board for testing purposes.",
    "messages": null
}

2) Post a new Message

POST - https://localhost:44384/api/Messages

Body - Raw - JSON

{
    "Text":"Posting my first message!",
    "User":"Jesse",
    "PostedDate":"1/1/2019",

    "MessageBoardId":4
}

Returns

{
    "id": 2,
    "text": "Posting my first message!",
    "user": "Jesse",
    "postedDate": "2019-01-01T00:00:00",
    "messageBoardId": 4,
    "messageBoard": null
}

I would expect that the messageBoard would not be null, and it would instead return the JSON for the messageBoard that was previously created. If I change to a GET method, it is also null. Why is it null?

EDIT: Here are my controllers. I removed actions except for GET and POST.

[Route("api/[controller]")]
[ApiController]
public class MessageBoardsController : ControllerBase
{
        private readonly MessageBoardContext _context;

        public MessageBoardsController(MessageBoardContext context)
        {
            _context = context;
        }

        // GET: api/MessageBoards/5
        [HttpGet("{id}")]
        public async Task<ActionResult<MessageBoard>> GetMessageBoard(long id)
        {
            var messageBoard = await _context.MessageBoards.FindAsync(id);

            if (messageBoard == null)
            {
                return NotFound();
            }

            return messageBoard;
        }


        // POST: api/MessageBoards
        [HttpPost]
        public async Task<ActionResult<MessageBoard>> PostMessageBoard(MessageBoard messageBoard)
        {
            _context.MessageBoards.Add(messageBoard);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetMessageBoard", new { id = messageBoard.Id }, messageBoard);
        }
}


[Route("api/[controller]")]
[ApiController]
public class MessagesController : ControllerBase
{
        private readonly MessageBoardContext _context;

        public MessagesController(MessageBoardContext context)
        {
            _context = context;
        }

        // GET: api/Messages/5
        [HttpGet("{id}")]
        public async Task<ActionResult<Message>> GetMessage(long id)
        {
            var message = await _context.Messages.FindAsync(id);

            if (message == null)
            {
                return NotFound();
            }

            return message;
        }


        // POST: api/Messages
        [HttpPost]
        public async Task<ActionResult<Message>> PostMessage(Message message)
        {
            _context.Messages.Add(message);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetMessage", new { id = message.Id }, message);
        }
}
1
0
2/8/2019 4:21:12 PM

Accepted Answer

You need to load related data

So for example, for your MessageBoard GET - // GET: api/MessageBoards/5

Change from:

var messageBoard = await _context.MessageBoards.FindAsync(id);

To

var messageBoard = await _context.MessageBoards
                        .Include(i=>i.Messages)
                        .FirstOrDefaultAsync(i => i.Id == id);
2
2/8/2019 4:11:34 PM

Popular Answer

I would expect that the messageBoard would not be null, and it would instead return the JSON for the messageBoard that was previously created. If I change to a GET method, it is also null. Why is it null?

This is because you are returning the newly created message, here only MessageBoadId is exists, not MessageBoad object. So you have to load the related MessageBoad from database using Include for newly created message.

Your PostMessage method should be as follows:

// POST: api/Messages
[HttpPost]
public async Task<ActionResult<Message>> PostMessage(Message message)
{
    _context.Messages.Add(message);
    await _context.SaveChangesAsync();

    var message = await _context.Messages
                    .Include(i=>i.MessageBoard)
                    .FirstOrDefaultAsync(i => i.Id == message.Id);

    return Json(message);
}


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow