diff --git a/src/EntityFramework.MappingAPI/EfMap.cs b/src/EntityFramework.MappingAPI/EfMap.cs
index ab24b6b..258ed11 100644
--- a/src/EntityFramework.MappingAPI/EfMap.cs
+++ b/src/EntityFramework.MappingAPI/EfMap.cs
@@ -1,6 +1,6 @@
using EntityFramework.MappingAPI.Mappings;
using System;
-using System.Collections.Generic;
+using System.Collections.Concurrent;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
@@ -12,9 +12,14 @@ namespace EntityFramework.MappingAPI
internal class EfMap
{
///
- ///
+ /// Mappings are cached per model for the lifetime of the process. A concurrent dictionary is
+ /// required, not just convenient: BulkInsert is reachable from several threads at once (any
+ /// app that bulk inserts from concurrent jobs or requests), and a plain Dictionary being
+ /// written on one thread while another reads it can throw or return the wrong entry. The
+ /// caller then sees an unrelated failure - via MappedDataReader, which catches per entity
+ /// type, that surfaces as the misleading "No table mappings provided."
///
- private static readonly Dictionary Mappings = new Dictionary();
+ private static readonly ConcurrentDictionary Mappings = new ConcurrentDictionary();
///
///
@@ -60,14 +65,11 @@ public static DbMapping Get(DbContext context)
cacheKey = iDbModelCacheKeyProvider.CacheKey;
}
- DbMapping mapping;
- if (Mappings.TryGetValue(cacheKey, out mapping))
- return mapping;
-
- mapping = new DbMapping(context);
-
- Mappings[cacheKey] = mapping;
- return mapping;
+ // GetOrAdd rather than TryGetValue-then-assign: two threads racing here used to corrupt
+ // the cache. They may still both build a DbMapping on first use and one result is
+ // discarded, which is wasteful but correct - and it keeps a failed build from being
+ // cached, which a Lazy would not.
+ return Mappings.GetOrAdd(cacheKey, _ => new DbMapping(context));
}
}
}