|
NullReferenceException results from the following code after upgrade to v2.4.2:
System.NullReferenceException occurred
|
Message: Exception thrown: 'System.NullReferenceException' in MongoDB.Bson.dll
|
Additional information: Object reference not set to an instance of an object.
|
Here's our C# code:
public class FxRateDataDb : IFxRateDataDb
|
{
|
[BsonId]
|
public string Id { get; set; }
|
}
|
|
public class MongoDbCollection<T, TId> : IDbCollection<T> where T : IId<TId>
|
{
|
private readonly IMongoCollection<T> _mongoCollection;
|
|
public async Task ReplaceOneAsync(T item, bool upsert)
|
{
|
await _mongoCollection.ReplaceOneAsync(x => Equals(x.Id, item.Id), item, new UpdateOptions {IsUpsert = upsert}); // NullReferenceException
|
}
|
}
|
And here's the stack trace:
> MongoDB.Bson.dll!MongoDB.Bson.Serialization.IBsonSerializerExtensions.Serialize(MongoDB.Bson.Serialization.IBsonSerializer serializer, MongoDB.Bson.Serialization.BsonSerializationContext context, object value) Unknown
|
MongoDB.Bson.dll!MongoDB.Bson.Serialization.IBsonSerializerExtensions.ToBsonValue(MongoDB.Bson.Serialization.IBsonSerializer serializer, object value) Unknown
|
MongoDB.Driver.dll!MongoDB.Driver.Linq.Translators.PredicateTranslator.TranslateComparison(System.Linq.Expressions.Expression variableExpression, System.Linq.Expressions.ExpressionType operatorType, System.Linq.Expressions.ConstantExpression constantExpression) Unknown
|
MongoDB.Driver.dll!MongoDB.Driver.Linq.Translators.PredicateTranslator.Translate(System.Linq.Expressions.Expression node) Unknown
|
MongoDB.Driver.dll!MongoDB.Driver.Linq.Translators.PredicateTranslator.Translate(System.Linq.Expressions.Expression node, MongoDB.Bson.Serialization.IBsonSerializerRegistry serializerRegistry) Unknown
|
MongoDB.Driver.dll!MongoDB.Driver.MongoCollectionImpl<SPMO.Providers.MarketData.FxRateDataDb>.ConvertWriteModelToWriteRequest(MongoDB.Driver.WriteModel<SPMO.Providers.MarketData.FxRateDataDb> model, int index) Unknown
|
System.Core.dll!System.Linq.Enumerable.SelectIterator<MongoDB.Driver.WriteModel<SPMO.Providers.MarketData.FxRateDataDb>, MongoDB.Driver.Core.Operations.WriteRequest>(System.Collections.Generic.IEnumerable<MongoDB.Driver.WriteModel<SPMO.Providers.MarketData.FxRateDataDb>> source, System.Func<MongoDB.Driver.WriteModel<SPMO.Providers.MarketData.FxRateDataDb>, int, MongoDB.Driver.Core.Operations.WriteRequest> selector) Unknown
|
MongoDB.Driver.Core.dll!MongoDB.Driver.Core.Operations.BulkMixedWriteOperation.BatchHelper.FindOrderedRuns(int maxRunLength) Unknown
|
MongoDB.Driver.Core.dll!MongoDB.Driver.Core.Misc.ReadAheadEnumerable<MongoDB.Driver.Core.Operations.BulkMixedWriteOperation.Run>.ReadAheadEnumerator.MoveNext() Unknown
|
MongoDB.Driver.Core.dll!MongoDB.Driver.Core.Operations.BulkMixedWriteOperation.BatchHelper.GetBatches() Unknown
|
MongoDB.Driver.Core.dll!MongoDB.Driver.Core.Operations.BulkMixedWriteOperation.ExecuteAsync(MongoDB.Driver.Core.Bindings.IWriteBinding binding, System.Threading.CancellationToken cancellationToken) Unknown
|
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) Unknown
|
|
|
With that version of the test program I can reproduce the NullReferenceException with 2.4.2. The extra levels of indirection must be what made the difference.
The NullReferenceException is gone in 2.4.3.
Note that by declaring the Id using an interface the serialized form of the Id value includes a "_t" discriminator value.
Your work around is fine for 2.4.2, and should no longer be necessary in 2.4.3.
|
|
Try this:
|
using System;
|
using System.ComponentModel;
|
using MongoDB.Bson.Serialization.Attributes;
|
using MongoDB.Driver;
|
|
namespace TestCSharp1922
|
{
|
public interface IAuditTrailKey
|
{
|
[BsonElement("D", Order = 0)]
|
DateTime KeyDateTime { get; set; }
|
[BsonElement("S", Order = 1)]
|
string KeyString { get; set; }
|
[BsonElement("I", Order = 2)]
|
int KeyInt { get; set; }
|
}
|
|
public class AuditTrailEntryId : IAuditTrailKey
|
{
|
[BsonElement("D", Order = 0)]
|
public DateTime KeyDateTime { get; set; }
|
[BsonElement("S", Order = 1)]
|
public string KeyString { get; set; }
|
|
[BsonElement("I", Order = 2)]
|
public int KeyInt { get; set; }
|
|
public override string ToString()
|
{
|
return $"{KeyDateTime}:{KeyString}:{KeyInt}";
|
}
|
}
|
|
public interface IAuditTrail<TKey> where TKey : IAuditTrailKey
|
{
|
[BsonId()]
|
TKey Id { get; set; }
|
}
|
|
public class AuditTrailEntry : IAuditTrail<IAuditTrailKey>
|
{
|
|
[BsonId()]
|
public IAuditTrailKey Id { get; set; }
|
}
|
|
public static class Program
|
{
|
public static void Main(string[] args)
|
{
|
|
var id = new AuditTrailEntryId
|
{
|
KeyDateTime = DateTime.UtcNow,
|
KeyString = "abc",
|
KeyInt = 123
|
};
|
var entry = new AuditTrailEntry() { Id = id };
|
|
|
DoMethod<AuditTrailEntry,IAuditTrailKey>(entry);
|
}
|
|
public static void DoMethod<TAuditTrail,TKey>(TAuditTrail entry) where TAuditTrail : IAuditTrail<TKey>
|
where TKey : IAuditTrailKey
|
{
|
var client = new MongoClient("mongodb://localhost");
|
var database = client.GetDatabase("test");
|
var collection = database.GetCollection<TAuditTrail>("test");
|
|
var options = new UpdateOptions { IsUpsert = true };
|
// Works..
|
var filter = new FilterDefinitionBuilder<TAuditTrail>().Eq(tad => tad.Id, entry.Id);
|
var result = collection.ReplaceOneAsync(filter, entry, options).GetAwaiter().GetResult();
|
|
// Fails..
|
var filter2 = new ExpressionFilterDefinition<TAuditTrail>(tad => tad.Id.Equals(entry.Id));
|
var result2 = collection.ReplaceOneAsync(filter2, entry, options).GetAwaiter().GetResult();
|
}
|
}
|
}
|
|
|
|
paul.reed I am unable to reproduce any error with your example. I get no exception and the resulting filter looks correct.
I had to guess at some code to get something that compiled and ran. This is what I tested with. Let me know if I need to change anything to reproduce the error you are seeing. I tested against 2.4.2.
using System;
|
using MongoDB.Bson.Serialization.Attributes;
|
using MongoDB.Driver;
|
|
namespace TestCSharp1922
|
{
|
public interface IAuditTrailKey
|
{
|
DateTime KeyDateTime { get; set; }
|
string KeyString { get; set; }
|
int KeyInt { get; set; }
|
}
|
|
public class AuditTrailEntryId : IAuditTrailKey
|
{
|
[BsonElement("D", Order = 0)]
|
public DateTime KeyDateTime { get; set; }
|
[BsonElement("S", Order = 1)]
|
public string KeyString { get; set; }
|
|
[BsonElement("I", Order = 2)]
|
public int KeyInt { get; set; }
|
|
public override string ToString()
|
{
|
return $"{KeyDateTime}:{KeyString}:{KeyInt}";
|
}
|
}
|
|
public class C
|
{
|
public AuditTrailEntryId Id { get; set; }
|
}
|
|
public static class Program
|
{
|
public static void Main(string[] args)
|
{
|
var client = new MongoClient("mongodb://localhost");
|
var database = client.GetDatabase("test");
|
var collection = database.GetCollection<C>("test");
|
|
var id = new AuditTrailEntryId
|
{
|
KeyDateTime = DateTime.UtcNow,
|
KeyString = "abc",
|
KeyInt = 123
|
};
|
var entry = new C { Id = id };
|
|
var filter = new ExpressionFilterDefinition<C>(tad => tad.Id.Equals(entry.Id));
|
var options = new UpdateOptions { IsUpsert = true };
|
var result = collection.ReplaceOneAsync(filter, entry, options).GetAwaiter().GetResult();
|
}
|
}
|
}
|
|
|
With my example shown - I get the exact same error. So I think it is the same issue.
Do you not find that
await _mongoCollection.ReplaceOneAsync(new FilterDefinitionBuilder<T>().Eq( tad=>tad.Id, item.Id), item, new UpdateOptions {IsUpsert = upsert}); // NullReferenceException
|
fixes the issue ?
|
|
paul.reed I was not able to reproduce your issue.
In any case, it looks like it might be something different. If you can provide more information to reproduce please open a new ticket as the original issue reported in this ticket has been fixed and this ticket is closed.
|
|
I can also confirm that this has the same underlying issue as CSHARP-1894 which is fixed in 2.4.3, so I am closing this ticket as fixed in 2.4.3.
|
|
I gave another try at reproducing this, this time using code as close as yours as possible. I had to add a few minor things to get it to compile.
I can now reproduce the NullReferenceException in 2.4.2, and can confirm that there was no NullReferenceException in 2.4.1. So I am changing the affects version back to 2.4.2.
My code is:
using System.Threading.Tasks;
|
using MongoDB.Bson.Serialization.Attributes;
|
using MongoDB.Driver;
|
|
namespace TestCSharp1922
|
{
|
public interface IId<out T>
|
{
|
T Id { get; }
|
}
|
|
public interface IFxRateDataDb : IId<string>
|
{
|
}
|
|
public class FxRateDataDb : IFxRateDataDb
|
{
|
[BsonId]
|
public string Id { get; set; }
|
}
|
|
public class Program
|
{
|
public static void Main(string[] args)
|
{
|
var client = new MongoClient("mongodb://localhost");
|
var database = client.GetDatabase("test");
|
var collection = database.GetCollection<FxRateDataDb>("test");
|
|
var mongoDbCollection = new MongoDbCollection<FxRateDataDb, string>(collection);
|
var item = new FxRateDataDb { Id = "abc" };
|
mongoDbCollection.ReplaceOneAsync(item, upsert: true).GetAwaiter().GetResult();
|
}
|
|
}
|
|
public class MongoDbCollection<T, TId> where T : IId<TId>
|
{
|
private readonly IMongoCollection<T> _mongoCollection;
|
|
public MongoDbCollection(IMongoCollection<T> collection)
|
{
|
_mongoCollection = collection;
|
}
|
|
public async Task ReplaceOneAsync(T item, bool upsert)
|
{
|
await _mongoCollection.ReplaceOneAsync(x => Equals(x.Id, item.Id), item, new UpdateOptions { IsUpsert = upsert }); // NullReferenceException
|
}
|
}
|
}
|
|
|
This worked for me - and I found it in 2.4.2
Replace:
result = col.ReplaceOneAsync(new ExpressionFilterDefinition<T>(tad => tad.Id.Equals(entry.Id)), entry, options);
|
With:
result = col.ReplaceOneAsync(new FilterDefinitionBuilder<T>().Eq( tad=>tad.Id, entry.Id), entry, options);
|
So I am guessing an issue with ExpressionFilterDefinition.
My Id was a composite:
|
public class AuditTrailEntryId : IAuditTrailKey
|
{
|
[BsonElement("D", Order=0)] public DateTime KeyDateTime { get; set; }
|
[BsonElement("S", Order = 1)] public string KeyString { get; set; }
|
|
[BsonElement("I", Order = 2)] public int KeyInt { get; set; }
|
|
public override string ToString()
|
{
|
return $"{KeyDateTime}:{KeyString}:{KeyInt}";
|
}
|
}
|
|
|
|
Downgrading back to 2.2.3 in the meantime.
|
|
Yeah, actually the suggested work-around is what we had in the codebase originally - same NullReferenceException.
None of our id's are ints. They are all either strings or Guids.
[edit: the Guids are stored as strings too]
|
|
Also, I get the exception in the previous comment when I use IId<int>, but not when I use IId<string>.
|
|
Also, I don't get a NullReferenceException when I attempt to reproduce this. I get:
Unhandled Exception: System.ArgumentException: Expression of type 'System.Int32' cannot be used for parameter of type 'System.Object' of method 'Boolean Equals(System.Object, System.Object)'
|
I don't think we support expressions that use the static Object.Equals method.
I've changed the affects version to 2.3 because this doesn't seem to be an issue introduced in 2.4.2.
|
|
As a workaround for now it looks like you could use:
x => x.Id.Equals(item.Id)
|
instead of:
x => Equals(x.Id, item.Id)
|
As the first parameter to ReplaceOneAsync.
|
|
Unable to edit the sample code - it would suffice here if FxRateDataDb inherited IId<string>, where:
public interface IId<out T>
|
{
|
T Id { get; }
|
}
|
|
Generated at Wed Feb 07 21:41:04 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.