Consider this stack trace from an exception thrown by Entity Framework 6.1.3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
System.InvalidOperationException : Sequence contains no matching element at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source, Func`2 predicate) at System.Data.Entity.Utilities.DbProviderManifestExtensions.GetStoreTypeFromName(DbProviderManifest providerManifest, String name) at System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive.PrimitivePropertyConfiguration.ConfigureColumn(EdmProperty column, EntityType table, DbProviderManifest providerManifest) at System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive.PrimitivePropertyConfiguration.Configure(EdmProperty column, EntityType table, DbProviderManifest providerManifest, Boolean allowOverride, Boolean fillFromExistingConfiguration) at System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive.PrimitivePropertyConfiguration.<>c__DisplayClass4.<Configure>b__3(Tuple`2 pm) at System.Data.Entity.Utilities.IEnumerableExtensions.Each[T](IEnumerable`1 ts, Action`1 action) at System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive.PrimitivePropertyConfiguration.Configure(IEnumerable`1 propertyMappings, DbProviderManifest providerManifest, Boolean allowOverride, Boolean fillFromExistingConfiguration) at System.Data.Entity.ModelConfiguration.Configuration.Types.StructuralTypeConfiguration.ConfigurePropertyMappings(IList`1 propertyMappings, DbProviderManifest providerManifest, Boolean allowOverride) at System.Data.Entity.ModelConfiguration.Configuration.Types.EntityTypeConfiguration.ConfigurePropertyMappings(DbDatabaseMapping databaseMapping, EntityType entityType, DbProviderManifest providerManifest, Boolean allowOverride) at System.Data.Entity.ModelConfiguration.Configuration.Types.EntityTypeConfiguration.Configure(EntityType entityType, DbDatabaseMapping databaseMapping, DbProviderManifest providerManifest) at System.Data.Entity.ModelConfiguration.Configuration.ModelConfiguration.ConfigureEntityTypes(DbDatabaseMapping databaseMapping, ICollection`1 entitySets, DbProviderManifest providerManifest) at System.Data.Entity.ModelConfiguration.Configuration.ModelConfiguration.Configure(DbDatabaseMapping databaseMapping, DbProviderManifest providerManifest) at System.Data.Entity.DbModelBuilder.Build(DbProviderManifest providerManifest, DbProviderInfo providerInfo) at System.Data.Entity.DbModelBuilder.Build(DbConnection providerConnection) at System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext) at System.Data.Entity.Internal.RetryLazy`2.GetValue(TInput input) at System.Data.Entity.Internal.LazyInternalContext.InitializeContext() at System.Data.Entity.Internal.InternalContext.ForceOSpaceLoadingForKnownEntityTypes() at System.Data.Entity.DbContext.System.Data.Entity.Infrastructure.IObjectContextAdapter.get_ObjectContext() |
Lucky for me, I know which tiny change I made to the code I am working with to induce the exception, and therefore I am pretty confident that I know what caused it. Had I not been so fortunate, this stack trace would have provided almost no help at all with diagnosing what is actually wrong.
Let’s try and decompile the topmost method, GetStoreTypeFromName, which is the one that apparently calls .Single(..) on a sequence without any matches. It looks like this:
1 2 3 4 5 6 7 |
internal static class DbProviderManifestExtensions { public static PrimitiveType GetStoreTypeFromName(this DbProviderManifest providerManifest, string name) { return providerManifest.GetStoreTypes().Single<PrimitiveType>((Func<PrimitiveType, bool>) (p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase))); } } |
This, Ladies And Gentlemen, is in my humble opinion almost unforgivable: It is simply not OK to call .Single(..) on stuff without re-throwing the exception along with information on what we were looking for.
A much improved version of the method would have looked like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
internal static class DbProviderManifestExtensions { public static PrimitiveType GetStoreTypeFromName(this DbProviderManifest providerManifest, string name) { try { return providerManifest.GetStoreTypes() .Single<PrimitiveType>(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)); } catch(Exception exception) { throw new ArgumentException($"Could not find PrimitiveType named '{name}'", exception); } } } |
this way not losing the original exception, still providing information from the callsite telling the caller what’s wrong.
An even better version of the method would provide me with a list of available “store types”, which often provides some great hints about what is wrong:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
internal static class DbProviderManifestExtensions { public static PrimitiveType GetStoreTypeFromName(this DbProviderManifest providerManifest, string name) { var storeTypes = providerManifest.GetStoreTypes(); try { return storeTypes .Single<PrimitiveType>(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)); } catch(Exception exception) { throw new ArgumentException($"Could not find PrimitiveType named '{name}' among the following store types: {string.Join(", ", storeTypes.Select(p => $"'{p.Name}'"))}", exception); } } } |
The next method in the stack trace is called ConfigureColumn, and sadly if you decompile the method, it will be just as cheap with regards to helping us know what was wrong, even though it has all kinds of valuable things to tell us like:
- which type was it trying to map
- which property could not be mapped
- which column in the database was it trying to map to
etc. etc. etc.
Please don’t write code like that.