NullReferenceException when trying to insert objects from a class that implements the IEnumerable<T> interface
See original GitHub issueHi!
Sorry for my bad english.
I’m using the version 4.0.0 downloaded from NuGet.
Below there is a example that works fine. A class containing a list of strings:
/// <summary>
/// Classe simples, contendo uma propriedade com listagem de strings
/// </summary>
class Example
{
[BsonId(true)]
public int Id { get; set; }
public List<string> Elements { get; set; }
public Example(string[] array)
{
this.Elements = new List<string>(array);
}
}
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main(params string[] args)
{
string[] ratings = new string[] { "AAA+", "AAA", "AAA-", "AAB", "BBB+", "BBB", "BBB-" };
using (LiteDatabase db = new LiteDatabase(Path.Combine(Base.ApplicationData, "LiteDB.db")))
{
LiteCollection<Example> ratingsllection = db.GetCollection<Example>("ratings");
ratingscollection.Insert(new Example(ratings));
}
}
Now if I make my class implements the IEnumerable<string> interface I get a NullReferenceException when i try to insert a new object. Example below:
/// <summary>
/// Classe contendo listagem de strings e que implementada a interface de enumeração
/// </summary>
class EnumerableExample : IEnumerable<string>
{
[BsonId(true)]
public int Id { get; set; }
public List<string> Elements { get; set; }
public EnumerableExample(string[] array)
{
this.Elements = new List<string>(array);
}
IEnumerator<string> IEnumerable<string>.GetEnumerator()
{
return this.Elements.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.Elements.GetEnumerator();
}
}
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main(params string[] args)
{
string[] ratings = new string[] { "AAA+", "AAA", "AAA-", "AAB", "BBB+", "BBB", "BBB-" };
using (LiteDatabase db = new LiteDatabase(Path.Combine(Base.ApplicationData, "LiteDB.db")))
{
LiteCollection<EnumerableExample> ratingscollection = db.GetCollection<EnumerableExample>("ratings");
ratingscollection.Insert(new EnumerableExample(ratings));
}
}
Now, if I create a class that don’t extends the IEnumerable<string> but this class contais a property from another class that extends the same interface then my object is saved whitout problems:
/// <summary>
/// Classe contendo listagem de strings e que implementada a interface de enumeração
/// </summary>
class EnumerableExample : IEnumerable<string>
{
public List<string> Elements { get; set; }
public EnumerableExample(string[] array)
{
this.Elements = new List<string>(array);
}
IEnumerator<string> IEnumerable<string>.GetEnumerator()
{
return this.Elements.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.Elements.GetEnumerator();
}
}
/// <summary>
/// Contem referência para a classe <see cref="EnumerableExample"/>
/// </summary>
class ContainsEnumerableExample
{
[BsonId(true)]
public int Id { get; set; }
public EnumerableExample Elements { get; set; }
public ContainsEnumerableExample(string[] array)
{
this.Elements = new EnumerableExample(array);
}
}
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main(params string[] args)
{
string[] ratings = new string[] { "AAA+", "AAA", "AAA-", "AAB", "BBB+", "BBB", "BBB-" };
using (LiteDatabase db = new LiteDatabase(Path.Combine(Base.ApplicationData, "LiteDB.db")))
{
LiteCollection<ContainsEnumerableExample> ratingscollection = db.GetCollection<ContainsEnumerableExample>("ratings");
ratingscollection.Insert(new ContainsEnumerableExample(ratings));
}
}
As this error was puzzling me, I downloaded the code from GitHub and used it in my project directly. When I try to insert my IEnumberable object I get the following stack:
Date: 18/12/2017 09:42:38
Type: NullReferenceException
Message: Object reference not set to an instance of an object.
Data:
Stack: at LiteDB.BsonMapper.ToDocument[T](T entity) in ..\LiteDB-4\LiteDB\Mapper\BsonMapper.Serialize.cs:line 29
at LiteDB.LiteCollection`1.<GetBsonDocs>d__35.MoveNext() in ..\LiteDB-4\LiteDB\Database\Collections\Insert.cs:line 65
at LiteDB.LiteEngine.<>c__DisplayClass20_0.<Insert>b__0(CollectionPage col) in ..\LiteDB-4\LiteDB\Engine\Engine\Insert.cs:line 33
at LiteDB.LiteEngine.Transaction[T](String collection, Boolean addIfNotExists, Func`2 action) in ..\LiteDB-4\LiteDB\Engine\LiteEngine.cs:line 211
at LiteDB.LiteEngine.Insert(String collection, IEnumerable`1 docs, BsonType autoId) in ..\LiteDB-4\LiteDB\Engine\Engine\Insert.cs:line 29
at LiteDB.LiteCollection`1.Insert(T document) in ..\LiteDB-4\LiteDB\Database\Collections\Insert.cs:line 18
As I debugged the process I found this segment in the class BsonMapper.Serialize.cs in the method internal BsonValue Serialize(Type type, object obj, int depth):
//// check if is a list or array
else if (obj is IEnumerable)
{
return this.SerializeArray(Reflection.GetListItemType(obj.GetType()), obj as IEnumerable, depth);
}
// otherwise serialize as a plain object
else
{
return this.SerializeObject(type, obj, depth);
}
As I see it when a object is IEnumerable it tries to saves ONLY It’s enumeration and ignores all the properties. And also the result from this.SerializeArray(Reflection.GetListItemType(obj.GetType()), obj as IEnumerable, depth); is not a BsonDocument and when It tries to convert to one we get the NullReferenceExeption above. If I comment out the verificaton else if (obj is IEnumerable) and jump to save the plain object I can save my IEnumerable class without problems.
It also shows why when I try to save a class that contains a property that is IEnumerable it saves it without problems. It first saves the ContainsEnumerableExample as a plain object and then saves recursively its IEnumerable property to a BsonArray. And there is no problems if a BsonArray (EnumerableExample ) is inside a BsonDocument (ContainsEnumerableExample).
The problem is when It converts a IEnumerable class to a BsonArray when it should be a BsonDocument instead.
Issue Analytics
- State:
- Created 6 years ago
- Comments:5 (2 by maintainers)
Top GitHub Comments
Olá Mauricio!
Inicialmente eu tentei utilizar o resgistro para tipo exclusivo/customizado da
BsonMapper
para contornar esse problema mas sem sucesso. Com isso fui tentar descobrir o que estava de “errado” com a inserção de classes que extendem interfaces.Com a sua resposta voltei a insistir no uso da
ResgisterType
e finalmente entendi o seu funcionamento. Agora consigo controlar o salvamento e o carregamento das minhas classes idependente da interface ou herença que elas venham a possuir.Muito obrigado e um abraço da terra do pão-de-queijo! 😉
Thanks!