【问题标题】: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.FaultExceptionExceptionDetail“具有相同键的项目已经添加。”这是一个使用 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 时似乎会发生异常。此方法似乎同时使用CreateGetAllCharts() 调用。

有人知道发生了什么以及如何解决此问题吗?

【问题讨论】:

    标签: c# mongodb wcf service channel


    【解决方案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();
      });
    

    【讨论】:

      猜你喜欢
      • 2011-06-18
      • 2014-12-18
      • 1970-01-01
      • 2018-06-29
      • 2010-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多