Skip to content

Commit

Permalink
Merge pull request #2384 from athendrix/master
Browse files Browse the repository at this point in the history
Bugfix for #2385 Also adds full record support by fixing this bug.
  • Loading branch information
mbdavid authored Nov 29, 2023
2 parents 4d65543 + 5845469 commit d6f0ca4
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 60 deletions.
40 changes: 40 additions & 0 deletions LiteDB.Tests/Mapper/CustomMappingCtor_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,33 @@ public MultiCtor(int id, string name)
this.Name = name;
}
}

public class MultiCtorWithArray
{
public int Id { get; set; }
public string[] StrArr { get; set; }
public string Name { get; set; }
public string DefinedOnlyInStrArray { get; set; }

public MultiCtorWithArray()
{
}

[BsonCtor]
public MultiCtorWithArray(int id, string[] strarr)
{
this.Id = id;
this.StrArr = strarr;
this.DefinedOnlyInStrArray = "changed";
}

public MultiCtorWithArray(int id, string[] strarr, string name)
{
this.Id = id;
this.StrArr = strarr;
this.Name = name;
}
}

public class MyClass
{
Expand Down Expand Up @@ -109,6 +136,19 @@ public void BsonCtor_Attribute()
obj.Name.Should().Be("value-name");
obj.DefinedOnlyInInt32.Should().Be("changed");
}

[Fact]
public void BsonCtorWithArray_Attribute()
{
var doc = new BsonDocument { ["_id"] = 25, ["name"] = "value-name", ["strarr"] = new BsonArray() {"foo","bar"} };

var obj = _mapper.ToObject<MultiCtorWithArray>(doc);

obj.Id.Should().Be(25);
obj.Name.Should().Be("value-name");
string.Join(", ", obj.StrArr).Should().Be("foo, bar");
obj.DefinedOnlyInStrArray.Should().Be("changed");
}

[Fact]
public void Custom_Ctor_Non_Simple_Types()
Expand Down
96 changes: 36 additions & 60 deletions LiteDB/Client/Mapper/BsonMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -379,77 +379,53 @@ protected virtual IEnumerable<MemberInfo> GetTypeMembers(Type type)
/// </summary>
protected virtual CreateObject GetTypeCtor(EntityMapper mapper)
{
var ctors = mapper.ForType.GetConstructors();

var ctor =
ctors.FirstOrDefault(x => x.GetCustomAttribute<BsonCtorAttribute>() != null && x.GetParameters().All(p => Reflection.ConvertType.ContainsKey(p.ParameterType) || _basicTypes.Contains(p.ParameterType) || p.ParameterType.GetTypeInfo().IsEnum)) ??
ctors.FirstOrDefault(x => x.GetParameters().Length == 0) ??
ctors.FirstOrDefault(x => x.GetParameters().All(p => Reflection.ConvertType.ContainsKey(p.ParameterType) || _customDeserializer.ContainsKey(p.ParameterType) || _basicTypes.Contains(p.ParameterType) || p.ParameterType.GetTypeInfo().IsEnum));

if (ctor == null) return null;

var pars = new List<Expression>();
var pDoc = Expression.Parameter(typeof(BsonDocument), "_doc");

// otherwise, need access ctor with parameter
foreach (var p in ctor.GetParameters())
Type type = mapper.ForType;
List<CreateObject> Mappings = new List<CreateObject>();
bool returnZeroParamNull = false;
foreach (ConstructorInfo ctor in type.GetConstructors())
{
// try first get converted named (useful for Id => _id)
var name = mapper.Members.FirstOrDefault(x => x.MemberName.Equals(p.Name, StringComparison.OrdinalIgnoreCase))?.FieldName ??
p.Name;

var expr = Expression.MakeIndex(pDoc,
Reflection.DocumentItemProperty,
new[] { Expression.Constant(name) });

if (_customDeserializer.TryGetValue(p.ParameterType, out var func))
{
var deserializer = Expression.Constant(func);
var call = Expression.Invoke(deserializer, expr);
var cast = Expression.Convert(call, p.ParameterType);
pars.Add(cast);
}
else if (_basicTypes.Contains(p.ParameterType))
ParameterInfo[] pars = ctor.GetParameters();
// For 0 parameters, we can let the Reflection.CreateInstance handle it, unless they've specified a [BsonCtor] attribute on a different constructor.
if (pars.Length == 0)
{
var typeExpr = Expression.Constant(p.ParameterType);
var rawValue = Expression.Property(expr, typeof(BsonValue).GetProperty("RawValue"));
var convertTypeFunc = Expression.Call(typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) }), rawValue, typeExpr);
var cast = Expression.Convert(convertTypeFunc, p.ParameterType);
pars.Add(cast);
returnZeroParamNull = true;
continue;
}
else if (p.ParameterType.GetTypeInfo().IsEnum && this.EnumAsInteger)
KeyValuePair<string, Type>[] paramMap = new KeyValuePair<string, Type>[pars.Length];
int i;
for (i = 0; i < pars.Length; i++)
{
var typeExpr = Expression.Constant(p.ParameterType);
var rawValue = Expression.PropertyOrField(expr, "AsInt32");
var convertTypeFunc = Expression.Call(typeof(Enum).GetMethod("ToObject", new Type[] { typeof(Type), typeof(Int32) }), typeExpr, rawValue);
var cast = Expression.Convert(convertTypeFunc, p.ParameterType);
pars.Add(cast);
ParameterInfo par = pars[i];
MemberMapper mi = null;
foreach (MemberMapper member in mapper.Members)
{
if (member.MemberName.ToLower() == par.Name.ToLower() && member.DataType == par.ParameterType)
{
mi = member;
break;
}
}
if (mi == null) {break;}
paramMap[i] = new KeyValuePair<string, Type>(mi.FieldName, mi.DataType);
}
else if (p.ParameterType.GetTypeInfo().IsEnum)
if (i < pars.Length) { continue;}
CreateObject toAdd = (BsonDocument value) =>
Activator.CreateInstance(type, paramMap.Select(x =>
this.Deserialize(x.Value, value[x.Key])).ToArray());
if (ctor.GetCustomAttribute<BsonCtorAttribute>() != null)
{
var typeExpr = Expression.Constant(p.ParameterType);
var rawValue = Expression.PropertyOrField(expr, "AsString");
var convertTypeFunc = Expression.Call(typeof(Enum).GetMethod("Parse", new Type[] { typeof(Type), typeof(string) }), typeExpr, rawValue);
var cast = Expression.Convert(convertTypeFunc, p.ParameterType);
pars.Add(cast);
return toAdd;
}
else
{
var propInfo = Reflection.ConvertType[p.ParameterType];
var prop = Expression.Property(expr, propInfo);
pars.Add(prop);
Mappings.Add(toAdd);
}
}

// get `new MyClass([params])` expression
var newExpr = Expression.New(ctor, pars.ToArray());

// get lambda expression
var fn = mapper.ForType.GetTypeInfo().IsClass ?
Expression.Lambda<CreateObject>(newExpr, pDoc).Compile() : // Class
Expression.Lambda<CreateObject>(Expression.Convert(newExpr, typeof(object)), pDoc).Compile(); // Struct

return fn;
if (returnZeroParamNull)
{
return null;
}
return Mappings.FirstOrDefault();
}

#endregion
Expand Down

0 comments on commit d6f0ca4

Please # to comment.