Using Entity Framework Core 7 code-first, I have two entities as follows:
User
public string UserId { get; set; }
[ForeignKey(nameof(UserId))]
public Queue Queue { get; set; }
Queue
public string QueueId { get; set; }
Each User always has one Queue, and the UserId (which is the primary key of User) also happens to be a foreign key to Queue.
Each Queue has 0..1 Users. I know it sounds strange, but think of UserId/QueueId as an email address that may be for a single user or a group. If it's a group, it doesn't tie back to a user, as the members of that group are worked out elsewhere. Hence the lack of a complimentary User property on the Queue entity.
Anyway, I would like to be able to access a user's queue via the nav property, so I added it along with the ForeignKey attribute shown above to tell it to use the UserId. The migration generated the following code, which looks correct as far as I can tell:
migrationBuilder.AddForeignKey(
    name: "FK_Users_Queues_UserId",
    table: "Users",
    column: "UserId",
    principalTable: "Queues",
    principalColumn: "QueueId",
    onDelete: ReferentialAction.NoAction);
...and from snapshot:
modelBuilder.Entity("Entities.User", b =>
{
    b.HasOne("Entities.Queue", "Queue")
        .WithMany()
        .HasForeignKey("UserId")
        .OnDelete(DeleteBehavior.Cascade)
        .IsRequired();
    b.Navigation("Queue");
});
But when I try to query a User and have it load the Queue property, for some reason it doesn't load it - Queue remains null even though there's a record in the table that matches.
var user = _context.Users
                   .Include(x => x.Queue)
                   .FirstOrDefault(x => x.UserId == userId);
Couple strange things I've noticed:
- If I add 
AsNoTracking(), it magically starts working: 
var user = _context.Users
                   .Include(x => x.Queue)
                   .AsNoTracking()
                   .FirstOrDefault(x => x.UserId == userId);
Oddly enough, I have other one-to-many collections on the
Userentity, and they all behave as expected. If I.Includeany of them, they are loaded and available upon query. For some reason this one-to-one is an oddball.If I use projection, the
Queueproperty is accessible regardless of whether I use an explicit.Include(which seems like it ties back to the lack of tracking since projections don't track):
var us = _context.Users
                 .Where(x => x.UserId == userId)
                 .Select(x => new { QueueId = x.Queue.QueueId });
I am using the default with EF Core 7, which I believe is lazy loading, and I don't have any desire to change that.
I'm just trying to understand the behavior. Is this expected? If so, what is the proper way of handling this?
UPDATE:
Apparently this has to do with how EF determines matches involving case-sensitivity around varchar keys. I had completely been glossing over the fact that my User.UserId and Queue.QueueId were only matches if the case-sensitivity of the database was being honored(SQL_Latin1_General_CP1_CI_AS/case insensitive). I guess because EF was able to match when not tracking, I never even considered it.
I still don't understand why the database collation isn't honored, but I certainly don't understand why tracking/notracking would make it change that behavior. I plan to open a bug on github later today, but they can provide some feedback.
To be clear, ultimately, I updated the Queue.QueueId value to be an exact match to the User.UserId and everything started working as expected.
UPDATE-2:
See the accepted answer I posted with a valid workaround involving the use of a custom value comparer.
Thank you!