我有以下精简版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
的{{1}}的WCF服务引用。
当我直接创建ChartServiceClient
的实例并注入MongoChartService
的实例(完全实现且没有子类)时,我可以完成数据库的往返(创建,读取,删除) 。如果我创建ChartDefinitionBase
的实例并尝试使用精简的DTO重复相同的步骤,则在调用ChartServiceClient
时ServiceModel.FaultException
会GetAllCharts
ExceptionDetail
&# 34;已经添加了具有相同密钥的项目。&#34;这是一个带注释的单元测试示例。
[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()
调用此方法。
任何人都知道发生了什么以及如何解决这个问题?
答案 0 :(得分:3)
问题似乎是由于在BsonClassMap.RegisterClassMap
的构造函数中放置了对MongoChartService
的调用。直接使用MongoChartService
时,只调用一次构造函数。使用ChartServiceClient
时,MongoChartService
构造函数在Create
上调用一次,在GetAllCharts
上调用一次;但是,由于ChartDefinitionBase
第一次注册,第二次注册它会产生异常。
当我在BsonIdAttribute
上使用Id
或在其他地方将呼叫转移到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说明了这一点,尽管我并不理解服务构造函数会被多次调用:
类映射的注册在之前发生是非常重要的 对他们来说是必要的。注册它们的最佳位置是app 在初始化与MongoDB的连接之前启动。
答案 1 :(得分:0)
如果您有多个使用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();
});