question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

KeyNotFoundException when adding a single projection

See original GitHub issue
              var result = await
                      _dbContext.Events
                      .Select(e => new ScheduledEventInfo()
                      {
                          FileNameLarge = e.Images.Any() ? e.Images.First().FileNameLarge : string.Empty,
                          FileNameMedium = e.Images.Any() ? e.Images.First().FileNameMedium : string.Empty
                      })
                      .ToListAsync();

This was working in previous versions. If I remove “FileNameMedium” or “FileNameLarge” then it can translate the SQL but with both FileNameLarge and FileNameMedium it crashes to “KeyNotFoundException: The given key ‘Outer’ was not present in the dictionary.”

Include provider and version information

EF Core version: 5.0.0 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: 5.0.0 Operating system: windows IDE: (e.g. Visual Studio 2019 16.3)

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:8 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
killswtchcommented, Nov 11, 2020

I’ve not managed to reproduce exactly the same error message in the demo app, but similar, and the triggers are the same. What I’m getting at the moment is System.Collections.Generic.KeyNotFoundException: 'The given key 'EmptyProjectionMember' was not present in the dictionary.' with and without using AutoMapper.

I’ve also hit a completely new problem if I remove the subqueries from AutoMapper Newtonsoft.Json.JsonSerializationException: 'Self referencing loop detected with type 'EfCoreOuterBug.TrainingRequest'. Path '[0].CourseInstance.Matches[0].EmployerSite.TrainingRequests'.' however as far as I can see the models are correctly constructed, and the relationships between them are detected (initially I explicitly set them, but the automatic entity configuration picks them up too).

using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace EfCoreOuterBug
{
	class Program
	{
		public static async Task Main()
		{
			using (var context = new SomeDbContext())
			{
				context.Database.EnsureDeleted();
				context.Database.EnsureCreated();

				context.Add(new CourseInstance
				{
					Name = "Test Course",
					Matches = new List<TrainingRequest>
					{
						new TrainingRequest {
							EmployerSite = new EmployerSite() {
								Name = "Test Employer"
							}
						}
					}
				});

				context.SaveChanges();
			}

			using (var context = new SomeDbContext())
			{
				var config = new MapperConfiguration(cfg => cfg.AddProfile<AutoMapperProfile>());
				var mapper = new Mapper(config);
				
				var result = await
					context.CourseInstances
						.Include(x => x.Matches).ThenInclude(x => x.EmployerSite)
						.Select(x => new DraftMatch { 
							Name = x.Name, 
							NominatedHostTrainingRequest = x.Matches.FirstOrDefault(x => x.IsNominatedHost), 
							AdditionalTrainingRequests = x.Matches.Where(x => !x.IsNominatedHost)
						})
						.ToListAsync();

				var autoMapperResult = await
					context.CourseInstances
						.Include(x => x.Matches).ThenInclude(x => x.EmployerSite)
						.ProjectTo<PublicDraftMatch>(config)
						.ToListAsync();

				Console.WriteLine(JsonConvert.SerializeObject(autoMapperResult));
			}
		}
	}

	public class SomeDbContext : DbContext
	{
		private static ILoggerFactory ContextLoggerFactory
			=> LoggerFactory.Create(b => b.AddConsole().SetMinimumLevel(LogLevel.Information));

		public DbSet<TrainingRequest> TrainingRequests { get; set; }
		public DbSet<EmployerSite> EmployerSites { get; set; }
		public DbSet<CourseInstance> CourseInstances { get; set; }

		protected override void OnModelCreating(ModelBuilder modelBuilder)
		{
			modelBuilder.Entity<CourseInstance>(e => {
				e.HasKey(t => t.CourseInstanceId);
			});

			modelBuilder.Entity<TrainingRequest>(e => {
				e.HasKey(t => t.TrainingRequestId);

				/*e.HasOne(t => t.EmployerSite)
					.WithMany(x => x.TrainingRequests)
					.HasForeignKey(x => x.EmployerSiteId);

				e.HasOne(t => t.MatchedCourseInstance)
					.WithMany(x => x.Matches)
					.HasForeignKey(x => x.MatchedCourseInstanceId);*/
			});

			modelBuilder.Entity<EmployerSite>(e => {
				e.HasKey(t => t.EmployerSiteId);
			});
		}

		protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
			=> optionsBuilder
				.UseSqlServer(ConnectionStringHere)
				.EnableSensitiveDataLogging()
				.UseLoggerFactory(ContextLoggerFactory);
	}

	// EF DTOs

	public class CourseInstance
	{
		public Guid CourseInstanceId { get; set; }

		public string Name { get; set; }

		public ICollection<TrainingRequest> Matches { get; set; }
	}

	public class TrainingRequest
	{
		public Guid TrainingRequestId { get; set; }

		[Required]
		public Guid EmployerSiteId { get; set; }
		public EmployerSite EmployerSite { get; set; }

		public Guid? MatchedCourseInstanceId { get; set; }
		public CourseInstance MatchedCourseInstance { get; set; }

		public bool IsNominatedHost { get; set; }
	}

	public class EmployerSite
	{
		public Guid EmployerSiteId { get; set; }

		[Required]
		public string Name { get; set; }

		public ICollection<TrainingRequest> TrainingRequests { get; set; }
	}

	public class DraftMatch
	{
		public string Name { get; set; }
		public CourseInstance CourseInstance { get; set; }
		public TrainingRequest NominatedHostTrainingRequest { get; set; }
		public IEnumerable<TrainingRequest> AdditionalTrainingRequests { get; set; }
	}

	// 'Public' objects

	public class PublicDraftMatch
	{
		public string Name { get; set; }
		public PublicCourseInstance CourseInstance { get; set; }
		public PublicTrainingRequest NominatedHostTrainingRequest { get; set; }
		public IEnumerable<PublicTrainingRequest> AdditionalTrainingRequests { get; set; }
	}

	public class PublicCourseInstance
	{
		public Guid CourseInstanceId { get; set; }

		public string Name { get; set; }

		public ICollection<TrainingRequest> Matches { get; set; }
	}

	public class PublicTrainingRequest
	{
		public Guid TrainingRequestId { get; set; }

		[Required]
		public Guid EmployerSiteId { get; set; }
		public EmployerSite EmployerSite { get; set; }

		public Guid? MatchedCourseInstanceId { get; set; }
		public CourseInstance MatchedCourseInstance { get; set; }

		public bool IsNominatedHost { get; set; }
	}

	public class PublicEmployerSite
	{
		public Guid EmployerSiteId { get; set; }

		[Required]
		public string Name { get; set; }

		public ICollection<PublicTrainingRequest> TrainingRequests { get; set; }
	}

	public class AutoMapperProfile : Profile
	{
		public AutoMapperProfile()
		{
			CreateMap<TrainingRequest, PublicTrainingRequest>();
			CreateMap<EmployerSite, PublicEmployerSite>();
			CreateMap<CourseInstance, PublicCourseInstance>();
			CreateMap<CourseInstance, PublicDraftMatch>()
				.ForMember(x => x.CourseInstance, o => o.MapFrom(s => s))
				.ForMember(x => x.NominatedHostTrainingRequest, o => o.MapFrom(s => s.Matches.FirstOrDefault(y => y.IsNominatedHost)))
				.ForMember(x => x.AdditionalTrainingRequests, o => o.MapFrom(s => s.Matches.Where(x => !x.IsNominatedHost)));
		}
	}
}

Both with and without AutoMapper, if the .Include(x => x.Matches).ThenInclude(x => x.EmployerSite) line is removed, the error is not triggered. It’s not strictly needed for this query, but in my real-world case, I need to include the related entities so that they are populated in the result.

Similarly, if either of the 2 subqueries are removed, the EmptyProjectionMember error is not triggered (but the unexpected loop detection is).

Edit: Incidentally, there is no query output by the logger when the EmptyProjectionMember exception is thrown.

0reactions
smitpatelcommented, Nov 13, 2020

I generated package from the PR of the fix. And the above reproducible code is fixed for both with/without AM. If anyone else can share their repro code, I can try out those out too.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Error projecting from a grouped linq query
My goal is to retrieve one image per (parent) event item but without the binary data being included. Using different answers on SO...
Read more >
KeyNotFoundException Class (System.Collections.Generic)
A KeyNotFoundException is thrown when an operation attempts to retrieve an element from a collection using a key that does not exist in...
Read more >
unhandled exception of type 'System.Collections.Generic. ...
The error message states: “An unhandled exception of type 'System.Collections.Generic.KeyNotFoundException' occurred in mscorlib.dll, additional ...
Read more >
C# KeyNotFoundException: Key Not Present in Dictionary
In a C# program, a KeyNotFoundException was thrown. This is likely caused by a lookup done on a key (one that is not...
Read more >
Entity Framework latest version - Awesome .NET - LibHunt
... KeyNotFoundException when adding a single projection ... ArgumentException: The collection argument 'propertyNames' must contain at least one element ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found