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; }
}
Related
I'm trying to serialize an object with Newtonsoft.Json by using the Fields from the MemberSerialization enum.
This is the class where [JsonObject(MemberSerialization.Fields)] is added:
[JsonObject(MemberSerialization.Fields)]
public class Employee {
[JsonIgnore]
public int ID { get; set; }
[JsonProperty]
public string Name { get; set; }
public char Gender { get; set; }
[JsonProperty]
public string WorkPhone { get; set; }
private string Address { get; set; }
[JsonProperty]
public List<string> Skills { get; set; }
}
//Main part:
Employee e1 = new Employee() {
ID = 1,
Name = "Igor",
Gender = 'M',
WorkPhone = "78123456",
Skills = new List<string>() { "C#", "SQL Server" }
};
string json = JsonConvert.SerializeObject(employees, Formatting.Indented);
string path = #"..\JsonSerialization.json";
File.WriteAllText(path, json);
Serialization gives an output which is not expected:
[
{
"<ID>k__BackingField": 1,
"<Name>k__BackingField": "Igor",
"<Gender>k__BackingField": "M",
"<WorkPhone>k__BackingField": "78123456",
"<Address>k__BackingField": "ul. Partizanski odredi, Skopje",
"<Skills>k__BackingField": [
"C#",
"SQL Server"
]
}
]
If I comment the class attribute
//[JsonObject(MemberSerialization.Fields)]
then the functionality works.
Any thoughts about this, why it's that? I can't find it specified in the Newtonsoft.Json documentation.
However, the JsonIgnore attribute works fine with
[JsonObject(MemberSerialization.OptOut)]
and
[JsonObject(MemberSerialization.OptIn)]
When you use an auto-property (i.e. { get; set; }), "the compiler creates a private, anonymous backing field that can only be accessed through the property's get and set accessors" (docs)
What you're seeing here is those private backing fields, as the docs for MemberSerialization.Fields say - "All public and private fields are serialized."
As all your properties are public and you have no fields in your class, why would you set that value anyway?
Even if I have fields and auto-properties, I expect only the fields to be serialized. However, the auto-properties are there also. It was just tried and we didn't expect any properties data in a serialized file. Now it's clear why the k__BackingField is there. Thanks!
I have 2 service models and 2 DAL models. While creating new Author, I want to save its books to Book table. So, I send payload as json. However, If I try to adapt model to Book model, its values are null.So I can solve this problem. I also tried model.Adapt<IEnumerable<Book>>(), this also comes null.
public async Task<AuthorRequest> CreateAsync(AuthorRequest model)
{
var authorEntity= model.Adapt<Author>(); // works fine
var bookEntity =model.Adapt<Book>();//null
}
Service.Models
public class AuthorRequest :Identifiable<string>
{
public string override Id{get;set;}
[Attr("name")]
public string Name { get; set; }
[Attr("surname")]
public string Surname { get; set; }
public ICollection<BookRequest> Books{get; set; }
}
public class BookRequest :Identifiable<string>
{
public string override Id{get;set;}
public string Title { get; set; }
}
DAL.Model
public class Author : AuditableEntity
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("surname")]
public string Surname { get; set; }
[JsonProperty("books")]
public Relationship<IList<Book>> Books;
}
public class Book :AuditableEntity
{
[JsonProperty("Title")]
public string Title { get; set; }
[JsonProperty("Author")]
public Relationship<IList<Author>> Author;
}
Mappings with mapster
TypeAdapterConfig<DAL.Models.Author, Service.Models.AuthorRequest>.NewConfig();
TypeAdapterConfig<DAL.Models.Book, Service.Models.AuthorRequest>.NewConfig();
TypeAdapterConfig<DAL.Models.Book, Service.Models.BookRequest>.NewConfig();
TypeAdapterConfig<Service.Models.AuthorRequest, DAL.Models.Author>.NewConfig();
TypeAdapterConfig<Service.Models.AuthorRequest, DAL.Models.Book>.NewConfig();
TypeAdapterConfig<Service.Models.BookRequest, DAL.Models.Book>.NewConfig();
AuthorRequest.JSON
{
"name": "William",
"surname": "Shakespeare",
"books": [{
"title": "Macheth"
}]
}
Why do you expect model.Adapt<Book>() to yield any result here? Mapping entities usually works by copying over similar properties. This works for AuthorRequest and Author because they both contain the same properties. So when you map the model, which is an AuthorRequest, you properly get a filled Author object back.
However, when you try to map the same AuthorRequest to a (single) Book then that won’t work since they are just too different: AuthorRequest has the properties Name, Surname, and Books, while Book has the properties Title and Author. There is simply no match there.
What you can do is map the books that are within the AuthorRequest object, doing this:
var bookEntities = model.Books.Adapt<Book[]>();
This will not map the collection of BookRequest objects that are within the model to a Book array. This should work as expected.
Note though that Mapster is already smart enough to map nested types on its own. If you look at the authorEntity object, you will see that the nested book entities were also properly mapped. So if you want to map the AuthorRequest (which contains BookRequests) to an Author entity containing Book entities, then your first line should already be enough:
var r = new AuthorRequest
{
Name = "William",
Surname = "Shakespeare",
Books = new BookRequest[] {
new BookRequest { Title = "Macbeth" },
new BookRequest { Title = "Romeo and Juliet" },
},
};
r.Adapt<Author>().Dump();
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 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.
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).