I have the following sample objects..
public class ComplexObject
{
public string Name { get; set; }
public SimpleObject Child1 { get; set; }
public SimpleObject Child2 { get; set; }
}
public class SimpleObject : IEquatable< SimpleObject >
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public int? Age { get; set; }
}
with the following AutoMapper configuration
Mapper.CreateMap<SimpleObject, SimpleObject>()
.ForAllMembers(expression=>expression.Condition(r=>!r.IsSourceValueNull));
Mapper.CreateMap<ComplexObject, ComplexObject>()
.ForAllMembers(expression=>expression.Condition(resolutionContext=>!resolutionContext.IsSourceValueNull));
and the following NUnit test...
[SetUp] public void Should_run_before_each_test()
{
child1 = new SimpleObject { FirstName = "Tom", LastName = "Smith", Age = 34, Gender = "Male" };
child2 = new SimpleObject { FirstName = "Andy", LastName = "Smith-bob", Age = 21, Gender = "Male" };
}
[ Test ]
public void Should_ignore_null_properties_in_nested_objects()
{
var source = new ComplexObject()
{
Name = "blue",
Child1 = new SimpleObject{FirstName = "dot", LastName = "net"}
};
var destination = new ComplexObject()
{
Name = "Andy",
Child1 = child1,
Child2 = child2
};
destination = Mapper.Map(source, destination);
destination.Name.Should(Be.EqualTo(source.Name));
destination.Child1.FirstName.Should(Be.EqualTo("dot"));
destination.Child1.LastName.Should(Be.EqualTo("net") );
destination.Child1.Age.Should(Be.EqualTo(child1.Age) );
destination.Child1.Gender.Should(Be.EqualTo(child1.Gender) );
}
The above test fails when asserting the age as AutoMapper is pushing null through to the destination object.
Am I expecting too much from AutoMapper, or have I missed some vital map configuration step.
The ultimate goal is to have a very complex domain object bound to incoming form data via an MVC action. AutoMapper will then be used to merge only non-null properties (at all depths of the object graph) into the real instance being maintained throughout a multi step form.
Just in case anyone needs to know... I have also tried the following mapping configuration without any luck :(
Mapper.CreateMap<ComplexObject, ComplexObject>()
.ForMember(x=>x.Child1, l=>l.ResolveUsing(x=>x.Child1 == null?null:Mapper.Map<SimpleObject,SimpleObject>(x.Child1)))
.ForMember(x=>x.Child2, l=>l.ResolveUsing(x=>x.Child2 == null?null:Mapper.Map<SimpleObject,SimpleObject>(x.Child2)))
.ForAllMembers(expression=>expression.Condition(resolutionContext=>!resolutionContext.IsSourceValueNull));
Why do you expect the value on the destination object is not null if the source value is null? The default value for int? is null, and therefore, when you call
destination.Child1.Age.Should(Be.EqualTo(child1.Age));
The .Should is what's failing. This should be expected behavior.
Try something like Assert.Null(detination.Child1.Age), and it should pass. AutoMapper is not 'pushing' the value through, there is no source value and therefore, Age is just going to have it's default value.
Related
I have a C# class with simple properties I am expecting to be saved to a Node when I call create. When I build the query using Neo4jClient 5.1.3, a complex class that is decorated with [JsonIgnore] is included in the CREATE command.
Here is a Person class:
public class Person {
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
[JsonIgnore]
public Organization Employer { get; set; }
}
I'm creating an instance of it:
var person = new Person
{
Email = "absdfasdf#12341234.com",
FirstName = "Job",
LastName = "Bob",
Employer = previouslyCreatedEmployer,
};
...and when I run this:
var query = neo.Client.Cypher
.Create("(p:Person:Security $object)")
.WithParams(new Dictionary<string, object> {
{ "object", person }
})
;
the QueryTextForDebug is this:
CREATE (p:Person:Security {
Email: "absdfasdf#12341234.com",
FirstName: "Job",
LastName: "Bob",
Employer: {
Logo: "",
IxId: "89becda5-98b4-4623-928f-4c6580cd554f",
name: "Joe's Bakery"
},
IxId: "eb117b37-b062-40e1-b038-1749156ecf7a"
})
Since I had the JsonIgnore tag on Employer in class definition I was not expecting it to be included. What am I missing? And thank you.
Side thought... maybe I SHOULDN'T have the JsonIgnore tag in there so I can write nicer queries, right?
Neo4jClient v5.1.3 depends on Newtonsoft.Json, check that JsonIgnore comes from this library and not from System.Text.Json, or specify full type name including namespace:
public class Person
{
[Newtonsoft.Json.JsonIgnore]
public Organization Employer { get; set; }
}
I have a set of messages which I cannot alter their structure, but all of them follow the same property structure after you drill into the first two. For example,
public class Animal {
public Dog Doggy { get; set; }
}
public class MixedAnimal {
public CatDog CatDoggy { get; set; }
}
public class Dog {
public Name name { get; set; }
public Age age { get; set; }
}
public class CatDog {
public Name name { get; set; }
public Age age { get; set; }
}
If I have a structure like this: SomeObj.Item where SomeObj is some object and Item is of type object which can hold either an Animal or MixedAnimal. How would I get to the value of either Dog or CatDog using the keyword dynamic?
I can get the top level object using SomeObj.Item as dynamic, and then do:
(SomeObj.Item as dynamic).Doggy.Name
but what I want is to just get the name without knowing the type of Item.
(SomeObj.Item as dynamic).(Something as dynamic).Name
Is this possible?
Using reflection is quite easy to resolve this problem. Something like this (general idea):
object animal = new Animal { Doggy = new Dog { age = 10, name = "Good boy" }};
var members = animal.GetType().GetMembers();
foreach (PropertyInfo member in members.Where(x => x is PropertyInfo))
{
if (member.PropertyType.Name == "Dog" || member.PropertyType.Name == "CatDog")
{
var propertyValue = member.GetValue(animal);
var propertyType = propertyValue.GetType();
var nameMember = propertyType.GetProperty("name");
var ageMember = propertyType.GetProperty("age");
var nameValue = nameMember.GetValue(propertyValue);
var ageValue = ageMember.GetValue(propertyValue);
Console.WriteLine($"Name: {nameValue}, Age: {ageValue}");
}
}
Everything you need to do additionally is providing list of type names which you want to process (like "Dog" or "CatDog" here).
I'm using Mapster to map Dto instances to Model objects.
The Dtos are sent by a Javascript client, sending only the properties updated.
I would like to ignore null values, and have Mapster leave the model instance unchanged for this properties.
A simplified example to better explain the scenario:
// My .Net Dto class, used for client/server communication.
public class PersonDto
{
public string Id { get; set; }
public string Name { get; set; }
public string Family { get; set; }
}
// My Model class. Let's assume is the same data as per the Dto.
public class Person
{
public string Id { get; set; }
public string Name { get; set; }
public string Family { get; set; }
}
public void Update()
{
var existingPerson = new Person
{
Id = "A",
Name = "Ned",
Family = "Stark"
};
var patchDataSentFromClient = new PersonDto
{
Id = "A",
Name = "Rob"
};
patchDataSentFromClient.Adapt(existingPerson);
// Here existingPerson.Family should be "Stark", but it gets emptied out.
// the mapping should be equivalent to:
// if (patchDataSentFromClient.Family != null) existingPerson.Family = patchDataSentFromClient.Family;
}
Edit: the point is I don't want to write down the mapping condition for each of the thousands of properties in my Dtos. I want Mapster to Automap all string properties by name, but keep the "patch-like" logic of ignoring null values.
You can use IgnoreNullValues.
I have the following classes.
public class Person {
public string Name { get; set; }
public Address Address { get; set; }
}
public class PersonDTO {
public string Name { get; set; }
public Address Address { get; set; }
}
I create a standard mapping using
Mapper.CreateMap<Person, PersonDTO>();
Then I'd like to map a Person into an existing PersonDTO hierarchy in that way that the existing Address will be updated instead of reference copied if you know what I mean.
var person = new Person() {
Name = "Test",
Address = new Address() {
Country = "USA",
City = "New York"
}
};
var personDTO = new PersonDTO() {
Name = "Test2",
Address = new Address() {
Country = "Canada",
City = "Ottawa"
}
};
Mapper.Map(person, personDTO);
I'd like to fulfill the following tests.
Assert.AreNotEqual(person.Address, personDTO.Address);
Assert.AreEqual(person.Address.Country, personDTO.Address.Country);
Assert.AreEqual(person.Address.City, personDTO.Address.City);
Just create a map between Address and itself like this:
Mapper.CreateMap<Address, Address>();
Please note that the following test:
Assert.AreNotEqual(person.Address, personDTO.Address);
Might not give you want you want if Address defines an Equals method. From what I understand from the question, you want to check for reference equality.
If you are using NUnit, you should use Assert.AreNotSame.
In general you can use object.ReferenceEquals to check for reference equality like this:
bool same_object = object.ReferenceEquals(person.Address, personDTO.Address);
I'm using MongoDB and official C# driver 0.9
I'm just checking how embedding simple documents works.
There are 2 easy classes:
public class User
{
public ObjectId _id { get; set; }
public string Name { get; set; }
public IEnumerable<Address> Addresses { get;set; }
}
public class Address
{
public ObjectId _id { get; set; }
public string Street { get; set; }
public string House { get; set; }
}
I create a new user:
var user = new User
{
Name = "Sam",
Addresses = (new Address[] { new Address { House = "BIGHOUSE", Street = "BIGSTREET" } })
};
collection.Insert(user.ToBsonDocument());
The user is successfully saved, so is his address.
After typing
db.users.find()
in MongoDB shell, I got the following result:
{ "_id" : ObjectId("4e572f2a3a6c471d3868b81d"), "Name" : "Sam", "Addresses" : [
{
"_id" : ObjectId("000000000000000000000000"),
"Street" : "BIGSTREET",
"House" : "BIGHOUSE"
}
] }
Why is address' object id 0?
Doing queries with the address works though:
collection.FindOne(Query.EQ("Addresses.Street", streetName));
It returns the user "Sam".
It's not so much a bug as a case of unmet expectations. Only the top level _id is automatically assigned a value. Any embedded _ids should be assigned values by the client code (use ObjectId.GenerateNewId). It's also possible that you don't even need an ObjectId in the Address class (what is the purpose of it?).
Use BsonId attribute:
public class Address
{
[BsonId]
public string _id { get; set; }
public string Street { get; set; }
public string House { get; set; }
}
Identifying the Id field or property
To identify which field or property of
a class is the Id you can write:
public class MyClass {
[BsonId]
public string SomeProperty { get; set; }
}
Driver Tutorial
Edit
It's actually not working. I will check later why.
If you need get it work use following:
[Test]
public void Test()
{
var collection = Read.Database.GetCollection("test");
var user = new User
{
Name = "Sam",
Addresses = (new Address[] { new Address { House = "BIGHOUSE", Street = "BIGSTREET", _id = ObjectId.GenerateNewId().ToString() } })
};
collection.Insert(user.ToBsonDocument());
}
Get the collection as User type:
var collection = db.GetCollection<User>("users");
Initialize the field _id as follows:
var user = new User
{
_id = ObjectId.Empty,
Name = "Sam",
Addresses = (new Address[] { new Address { House = "BIGHOUSE", Street = "BIGSTREET" } })
};
Then you insert the object:
collection.InsertOne(user);
The _id field will automatically be generated.
In this link you will find alternative ways to have customized auto-generated ID(s).