为啥 WCF/Mongo 抛出异常:已添加具有相同键的项目
Posted
技术标签:
【中文标题】为啥 WCF/Mongo 抛出异常:已添加具有相同键的项目【英文标题】:Why is WCF/Mongo throwing exception: An item with the same key has already been added为什么 WCF/Mongo 抛出异常:已添加具有相同键的项目 【发布时间】:2016-09-01 20:46:39 【问题描述】:我有以下精简的 DTO:
[DataContract]
public class ChartDefinitionBase
[DataMember]
public string Id get; private set;
...以及以下精简的 Mongo 服务定义:
public class MongoChartService : IChartService
private readonly IMongoCollection<ChartDefinitionBase> _collection;
private const string _connectionStringKey = "MongoChartRepository";
internal MongoChartService()
// Exception occurs here.
BsonClassMap.RegisterClassMap<ChartDefinitionBase>(cm =>
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetIdGenerator(StringObjectIdGenerator.Instance);
);
var connectionString = ConfigurationManager.ConnectionStrings[_connectionStringKey].ConnectionString;
var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString));
var client = new MongoClient(settings);
var database = client.GetDatabase(ConfigurationManager.ConnectionStrings[_connectionStringKey].ProviderName);
_collection = database.GetCollection<ChartDefinitionBase>("Charts");
public void Create(ChartDefinitionBase instance)
_collection.InsertOne(instance);
public IEnumerable<ChartDefinitionBase> GetAllCharts()
var charts = _collection.Find(_ => true).ToList();
return charts;
然后,我有一个客户端库,其中有一个对 MongoChartService
的 WCF 服务引用,名为 ChartServiceClient
。
当我直接创建MongoChartService
的实例并注入ChartDefinitionBase
的实例(完全实现且没有子类)时,我可以完成到数据库的往返(创建、读取、删除)。如果我创建ChartServiceClient
的实例并尝试使用精简的DTO 重复相同的步骤,当GetAllCharts
被调用时,我会得到ServiceModel.FaultException
,ExceptionDetail
“具有相同键的项目已经添加。”这是一个使用 cmets 的示例单元测试。
[TestMethod, TestCategory("MongoService")]
public void ChartServiceClient_CRD_ExecutesSuccessfully()
SetupHost();
using (var client = new ChartServiceClient())
client.Create(_dto); // Create method succeeds. Single entry in dB with Mongo-generated ID.
ChartDefinitionBase dto = null;
while (dto == null)
var dtos = client.GetAllCharts(); // Exception occurs here.
dto = dtos.SingleOrDefault(d => d.Id == _dto.Id);
client.Delete(_dto);
while (dto != null)
var dtos = client.GetAllCharts();
dto = dtos.SingleOrDefault(d => d.Id == _dto.Id);
堆栈跟踪如下:
Server stack trace:
at System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter)
at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at QRPad.Spc.DataLayer.Charts.Service.Client.ServiceReference.IChartService.GetAllCharts()
at QRPad.Spc.DataLayer.Charts.Service.Client.ServiceReference.ChartServiceClient.GetAllCharts()
编辑:请注意,调用BsonClassMap.RegisterClassMap
时似乎会发生异常。此方法似乎同时使用Create
和GetAllCharts()
调用。
有人知道发生了什么以及如何解决此问题吗?
【问题讨论】:
【参考方案1】:问题似乎是由于在MongoChartService
的构造函数中放置了对BsonClassMap.RegisterClassMap
的调用。直接使用MongoChartService
时,构造函数只被调用一次。使用ChartServiceClient
时,MongoChartService
构造函数在Create
上调用一次,在GetAllCharts
上调用一次;但是,由于第一次注册了ChartDefinitionBase
,第二次尝试注册它会产生异常。
当我在 Id
上使用 BsonIdAttribute
或将呼叫转移到其他地方 BsonClassMap.RegisterClassMap
时,问题得到解决,例如在对客户端的呼叫上方:
[TestMethod, TestCategory("MongoService")]
public void ChartServiceClient_CRD_ExecutesSuccessfully()
SetupHost();
BsonClassMap.RegisterClassMap<ChartDefinitionBase>(cm =>
cm.AutoMap();
cm.MapIdMember(c => c.Id).SetIdGenerator(StringObjectIdGenerator.Instance);
);
using (var client = new ChartServiceClient())
client.Create(_dto);
ChartDefinitionBase dto = null;
while (dto == null)
var dtos = client.GetAllCharts();
dto = dtos.SingleOrDefault(d => d.Id == _dto.Id);
client.Delete(_dto);
while (dto != null)
var dtos = client.GetAllCharts();
dto = dtos.SingleOrDefault(d => d.Id == _dto.Id);
MongoDb documentation 说明了这种情况,尽管我没有意识到服务构造函数会被多次调用:
类映射的注册在先发生是非常重要的 他们被需要。注册它们的最佳位置是在应用程序 在初始化与 MongoDB 的连接之前启动。
【讨论】:
由于它是一个全局配置,如果执行两次就会中断,您可以先检查它是否尚未在if(!BsonClassMap.IsClassMapRegistered(typeof(ChartDefinitionBase)))
注册【参考方案2】:
如果您有多个应用程序使用 MongoDB 层作为库,您可能更喜欢静态构造函数中的类映射,而不是每个应用程序的启动。
需要注意的一点是,如果您将映射放在通用的基类中,静态构造函数仍然可以被多次调用(每种类型一次)。我上面提到的IsClassMapRegistered
检查在大多数情况下都会有所帮助,但它不是线程安全的。如果您仍然收到异常,请查看堆栈跟踪。如果调用堆栈中有异步方法,您将遇到线程安全问题,其中两个线程都确定类映射未注册,但随后一个线程击败另一个线程,第二个线程引发异常。处理这个问题的最佳方法是为您的类映射使用单例,并将类映射包装在 lock
语句中。
public sealed class BsonClassMapper
private static BsonClassMapper instance = null;
private static readonly object _lock = new object();
public static BsonClassMapper Instance
get
if(instance == null)
instance = new BsonClassMapper();
return instance;
public BsonClassMapper Register<T>(Action<BsonClassMap<T>> classMapInitializer)
lock(_lock)
if(!BsonClassMap.IsClassMapRegistered(typeof(T)))
BsonClassMap.RegisterClassMap<T>(classMapInitializer);
return this;
您的用法如下所示:
BsonClassMapper.Instance
.Register<User>(cm =>
cm.Automap();
)
.Register<Order>(cm =>
cm.AutoMap();
);
【讨论】:
以上是关于为啥 WCF/Mongo 抛出异常:已添加具有相同键的项目的主要内容,如果未能解决你的问题,请参考以下文章