1

I have the following entites:

public class Ticket 
{
    public int Id { get; set; }
    public string RequestBy { get; set; }
    public int PriorityId { get; set; }

    public ApplicationUser Requester { get; set; }
    public Priority Priority { get; set; }
}

public class ApplicationUser
{
    public ApplicationUser()
    {
        Id = Guid.NewGuid().ToString();
    }

    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Priority
{
    public int Id { get; set; }
    public string Name { get; set; }
}

When I create a new Ticket as follows in my controller:

 public class BaseController : Controller
{
    protected readonly IUnitOfWork UnitOfWork;

    public BaseController(IUnitOfWork unitOfWork)
    {
        UnitOfWork = unitOfWork; 
    }
}

public class TicketController : BaseController
{
    public TicketController(IUnitOfWork unitOfWork) : base(unitOfWork) { }

    [HttpPost]
    public ActionResult CreateTicket(CreateTicketVM viewModel)
    {
         // repopulate dropdowns incase we need to return view
         // viewModel.Priorities is IEnumerable list
         viewModel.Priorities = UnitOfWork.PriorityRepository.GetPriorities();

        //validation code removed for brevity...

        var ticket = new Ticket
        {
           RequestBy = !string.IsNullOrEmpty(viewModel.RequestBy) ? viewModel.RequestBy : User.Identity.GetUserId(),
           PriorityId = viewModel.PriorityId != 0 ? viewModel.PriorityId : (int)PriorityLevel.Medium,
        };

        UnitOfWork.TicketRepository.Add(ticket);
    }
}

Now when I debug var ticket after calling .Add(ticket) why has it loaded the related navigation property Priority and not the Requester navigation property despite the RequestBy value being set? As you can see I haven't defined any virtual keywords against either navigation properties?

Strangley this in only happening for all navigation properties that are of ApplicationUser type. All other navigation properties seem to get loaded even without using the virtual keyword???

Using fluent api I have defined the RequestBy FK as follows:

modelBuilder.Entity<Ticket>()
 .HasRequired(x => x.Requester)
 .WithMany()
 .HasForeignKey(x => x.RequestBy);

Below is additional code to provide some more context.

Ticket Repository:

public class TicketRepository : ITicketRepository
{
    private readonly ApplicationDbContext _context;

    public TicketRepository(ApplicationDbContext context)
    {
        _context = context;
    }

    public void Add(Ticket ticket)
    {
        _context.Ticket.Add(ticket);
    }
}

Priority Repository:

public class PriorityRepository : IPriorityRepository
{
    private readonly ApplicationDbContext _context;

    public PriorityRepository(ApplicationDbContext context)
    {
        _context = context;
    }

    public IEnumerable<Priority> GetPriorities()
    {
        return _context.Priority.ToList();
    }
}

Unit of work:

public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDbContext _context;

    public ITicketRepository TicketRepository { get; private set; }
    public IPriorityRepository PriorityRepository { get; private set; }

    public UnitOfWork(ApplicationDbContext context)
    {
        _context = context;
        TicketRepository = new TicketRepository(_context);
        PriorityRepository = new PriorityRepository(_context);
    }

    public void Complete()
    {
        _context.SaveChanges();
    }
}
11
  • Please correct either the classes listing or the question. RequestBy is a string in a model, so it doesn't have a FirstName property Commented Nov 4, 2017 at 11:44
  • @zaitsman sorry that was a typo Commented Nov 4, 2017 at 11:47
  • If you are using the same db context instance to retrieve data as you are to save the data then it is probably because you have set the Navigation property for RequestBy but not the one for Priority (only the ID). Easy test would be to set Priority and see what happens (or use a new context to retrieve data). Or you could 'force' a reload from the db to test. Commented Nov 4, 2017 at 11:51
  • @Cal279 I have no idea what you are talking about. I'm using the same context and you can see from the posted code what navigation properties I have. If I debug var ticket after call to _context.Ticket.Add(ticket); I observe the above behaviour. Commented Nov 4, 2017 at 11:57
  • 1
    Since RequestBy is not following the convention for explicit FK property name (which in this case is RequesterId), you need data annotations/fluent API to associate it with Requester navigation property. Currently EF is using shadow (hidden) property called Requester_Id. Commented Nov 4, 2017 at 12:04

1 Answer 1

2

Neither navigation property should be loaded. What you see is most likely a result of the so called navigation property fixup feature - if entity is already tracked by the context, EF updates navigation properties of the entities referencing it even if you don't specifically request that.

You can easily verify that by using var _context = new YourDbContext(); local variable instead of the _context field. Or something like this:

var ticket = new Ticket
{
    RequestBy = !string.IsNullOrEmpty(viewModel.RequestBy) ? viewModel.RequestBy : User.Identity.GetUserId(),
    PriorityId = viewModel.PriorityId != 0 ? viewModel.PriorityId : (int)PriorityLevel.Medium,
};

bool priorityLoaded = _context.Priority.Local.Any(e => e.Id == ticket.PriorityId);
bool userLoaded = _context.ApplicationUser.Local.Any(e => e.Id == ticket.RequestBy);

_context.Ticket.Add(ticket);

According to your description, priorityLoaded should be true while userLoaded - false.

Sign up to request clarification or add additional context in comments.

2 Comments

I've updated my original post with some more code if that helps. I can't seem to figure out this behaviour. Please advise.
Your right. I just did a test by changing public IEnumerable<Priority> GetPriorities() { return _context.Priority.AsNoTracking().ToList(); } and it didn't get loaded.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.