为什么WCF / Mongo抛出异常:已添加具有相同密钥的项目

时间:2016-05-06 17:20:53

标签: c# mongodb wcf service channel

我有以下精简版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重复相同的步骤,则在调用ChartServiceClientServiceModel.FaultExceptionGetAllCharts 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时似乎发生异常。似乎同时使用CreateGetAllCharts()调用此方法。

任何人都知道发生了什么以及如何解决这个问题?

2 个答案:

答案 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();
  });