Nested collections in CSV Helper mapping - c#

I have a collection Car including fields for date of production, car name, production place - this is a new model named ProductionPlace including City, Address and Id, and there is another collection in Cars which is Creator - including name, surname and country.
So this basically looks like this
Cars
{
ProductionDate,
Name,
ProductionPlace
{
Id,
City,
Address
},
Creator
{
Name,
Surname,
Country
}
How can I map those using CSV Helper?
I'm getting "there is no field named Creator" for example. I can simply map fields but not collection nested in my main collection
Edit
My code as far looks like that
public void MapCar()
{
Map(mapping => mapping.Car).ConvertUsing(
row => { List<Car> car = new List<Car>
{
ProductionDate = row.GetField("ProductionDate"),
Name = row.GetField("Name"),
ProductionPlace = new ProductionPlace
{
Id = row.GetField("Id"),
City = row.GetField("City"),
Address = row.GetField("Address")
},
Creator = new Creator
{
Name = row.GetField("Name"),
Surname = row.GetField("Surname"),
Country = row.GetFiele("Country")
}
};)}
Both Creator and ProductionPlace are separate models. And Car is a model using those two as it's fields.

Really the only issue you have right now is that both Car and Creator have the same name for the property Name. If you use the CsvHelper name attribute, you can give them different names in your CsvHeader and the rest will map without you having to do anything else.
void Main()
{
var input = new StringBuilder();
input.AppendLine("ProductionDate,CarName,Id,City,Address,CreatorName,Surname,Country");
input.AppendLine("2021-06-03,Tesla,1,MyCity,MyAddress,Bob,Jones,MyCountry");
using (var reader = new StringReader(input.ToString()))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
var records = csv.GetRecords<Car>().Dump();
}
}
public class Car
{
public DateTime ProductionDate { get; set; }
[Name("CarName")]
public string Name { get; set; }
public ProductionPlace ProductionPlace { get; set; }
public Creator Creator { get; set; }
}
public class ProductionPlace
{
public int Id { get; set; }
public string City { get; set; }
public string Address { get; set; }
}
public class Creator
{
[Name("CreatorName")]
public string Name { get; set; }
public string Surname { get; set; }
public string Country { get; set; }
}

Related

Automapper Map lists with layered properties

I'm having some trouble mapping an object with multiple layers of properties to an object with a single layer of properties. Here's an example:
My destination class
public class Part
{
public string Name { get; set; }
public string PartNumber { get; set; }
public string Position { get; set; }
public IList<Part> ReplacedBy { get; set; } = new List<Part>();
}
My source classes
public class PartType
{
public string PartNumber { get; set; }
public PartInformationType Part { get; set; }
}
public class PartInformationType
{
public string Position { get; set; }
public string Name { get; set; }
public IList<PartType> ReplacedBy { get; set; } = new List<PartType>();
}
Note that the real objects have ALOT more properties in each layer so it would be a hassle to do a ForMember() on each affected property. Is there any automated way to do this for me?
Expected result:
This is giving me the expected result but only for one generation of parts, say that each replacedBy part is replaced by another part for 10 generations, that quickly becomes unmanageable
var part = Mapper.DynamicMap<Part>(result);
part.ReplacedBy = new List<ReplacementPart>();
foreach (var partType in result.ReplacedBy)
{
var replacementPart = Mapper.DynamicMap<ReplacementPart(partType.Part);
replacementPart.Name= partType.Name;
replacementPart.Position= partType.Position;
part.ReplacedBy.Add(replacementPart);
}
This is an interesting problem, and I happen to have solved a very similar one recently in one of my projects so hopefully my answer will suit your needs as well or at least get you going on the right track. I've adjusted the sample code somewhat to your case.
My destination model class (pretty much same as yours):
public class PartModel
{
public string Name { get; set; }
public string PartNumber { get; set; }
public string Position { get; set; }
public List<PartModel> ReplacedBy { get; set; }
}
My two source classes (also pretty much same as yours)
public class PartEntity
{
public string PartNumber { get; set; }
public PartEntityInformation PartEntityInformation { get; set; }
}
public class PartEntityInformation
{
public string Position { get; set; }
public string Name { get; set; }
public List<PartEntity> ReplacedBy { get; set; }
}
I have defined a static EntityMap with the configurations for my two mappings. As I have mentioned in the comments of your question - I am not specifying any particular member mapping config as Automapper will just map by convention because the property names match between my source and destination objects.
public static class EntityMap
{
public static IMapper EntityMapper { get; set; }
static EntityMap()
{
EntityMapper = new MapperConfiguration(config =>
{
config.CreateMap<PartEntity, PartModel>();
config.CreateMap<PartEntityInformation, PartModel>();
}).CreateMapper();
}
}
You can use EntityMap like below. Nothing special here, it will just yield me the base model where only PartNumber property is mapped.
var rootPart = GetPart();
var rootPartModel = EntityMap.EntityMapper.Map<PartModel>(rootPart);
In order to get the nested mappings for your ReplacedBy parts, you can use recursion (this is how I solved this nested mapping requirement in my project, there may be better solutions). In this method, I recursively map the nested child objects to their destination objects because the initial mapping will give me the number of items that there is in the nested ReplacedBy list.
public static void MapRecursively(PartModel partModel, PartEntity partEntity)
{
EntityMap.EntityMapper.Map(partEntity.PartEntityInformation, partModel);
if (partEntity.PartEntityInformation.ReplacedBy == null
|| partEntity.PartEntityInformation.ReplacedBy.Count == 0) return;
for (var i = 0; i < partModel.ReplacedBy.Count; i++)
{
MapRecursively(partModel.ReplacedBy[i], partEntity.PartEntityInformation.ReplacedBy[i]);
}
}
So you can now just use the rootPart and rootPartModel with this recursive method to map out of the rest of your nested objects.
MapRecursively(rootPartModel, rootPart);
This should work out of the box, I have provided the GetRootPart() method specification below as it is just the sample data I have used.
private static PartEntity GetPart()
{
var partEntityInfo = new PartEntityInformation
{
Name = "SomeName",
Position = "2",
ReplacedBy = new List<PartEntity>
{
new PartEntity
{
PartNumber = "22",
PartEntityInformation = new PartEntityInformation
{
Name = "SomeSubName"
}
},
new PartEntity
{
PartNumber = "33",
PartEntityInformation = new PartEntityInformation
{
Name = "33SubName",
Position = "33SubPosition",
ReplacedBy = new List<PartEntity>
{
new PartEntity
{
PartNumber = "444",
PartEntityInformation = new PartEntityInformation
{
Name = "444SubSubname"
}
}
}
}
}
}
};
var part = new PartEntity
{
PartNumber = "1",
PartEntityInformation = partEntityInfo
};
return part;
}

How to set list A equal to list B, where on B a property does not exist which does exist on A

I have a C# list which is of type Person. This list needs to be converted into JSON data format. The Person C# class look like this:
public class Person
{
public int ID { get; set; }
public static int PSID = 1;
public string name { get; set; }
public string nameToken { get; set; }
public double DOB { get; set; }
public List<Award> awards { get; set; }
public List<Link> links { get; set; }
public Person()
{
awards = new List<Award>();
links = new List<Link>();
ID = PSID;
PSID++;
}
}
As I am required to convert a C# list of type Person into JSON. I made another Class in C# called PersonJS. It is exactly like the Person C# class the only difference is that I have removed some of the properties that are not required in the JSON front-end. Namely: nameToken, PSID.
public class PersonJS
{
public int ID { get; set; }
public string name { get; set; }
public double DOB { get; set; }
public List<AwardJS> awards { get; set; }
public List<Link> links { get; set; }
}
One of the properties of PersonJS is a List called awards which is of Type AwardJS. A problem occurs below because I try and equal Person.awards List equal to PersonJS.awards List. However, they are of difference types so it is not possible to equal both lists. The reason why I have put them equal to different types is because the JSON data does not need all of the properties that I have used in C#. So I made two classes Award and AwardJS. The only difference is that Award contains a property called filmWebToken whereas AwardJS does not.
public class Award
{
public int filmID { get; set; }
public int categoryID { get; set; }
public string filmWebToken { get; set; }
}
public class AwardJS
{
public int filmID { get; set; }
public int categoryID { get; set; }
}
In my code I iterate over all of the properties in C# list of type Person and I attempt to create a personjs object and add it to a PersonJS C# list. The PersonJS list will go back to the front-end as JSON. However, because the award property in the class PersonJS is different to the award property in Person I get the error "Cannot implicitly convert type AwardJS to Award". The reason I get this error is because PersonJS does not contain filmWebToken which exists in the Person class. I don't want the filmWebToken to be in the PersonJS list as it is not meant to be a property in my JSON data. However, as there are property fields in Person.Award I still want access to: filmID and CategoryID how can I ignore/by-pass the filmWebToken field. This is what I have tried:
List<Person> allPersons = DataRepository.GetAllPersons(); // contains the C# data
List<PersonJS> personjs = new List<PersonJS>(); // empty to start with
foreach (var person in allPersons)
{
foreach (var award in person.awards)
{
personjs.Add(
new PersonJS
{
ID = person.ID,
links = person.links,
name = person.name,
DOB = person.DOB,
awards = person.awards // The types are not equal: Person contains filmWebToken whereas PersonJS does not
});
}
}
Add a method called ToAwardJS in Award:
public AwardJS ToAwardJS() {
return new AwardJS { filmID = this.filmID, categoryID = this.categoryID };
}
Then when you create the PersonJS object, do:
new PersonJS
{
ID = person.ID,
links = person.links,
name = person.name,
DOB = person.DOB,
awards = person.awards.Select(x => x.ToAwardJS()).ToList(),
});
What serializer are you using? Most provide attributes to specify which members to include in the serialization. For example, the DataContractJsonSerializer uses [DataContract] and [DataMember]. I think Json.net uses [JsonIgnore]. There's no need for multiple classes.
void Main()
{
var jsSer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(Person));
var p = new Person {
ID = 1,
name = "John",
DOB = 1234.5,
nameToken = "token"
};
string result = null;
using (var ms = new MemoryStream())
{
jsSer.WriteObject(ms, p);
byte[] json = ms.ToArray();
ms.Close();
result = Encoding.UTF8.GetString(json, 0, json.Length);
}
Console.WriteLine(result);
}
[DataContract]
public class Person
{
//included
[DataMember]
public int ID { get; set; }
[DataMember]
public string name { get; set; }
[DataMember]
public string nameToken { get; set; }
[DataMember]
public double DOB { get; set; }
//ignored
public static int PSID = 1;
public List<string> awards { get; set; }
public List<string> links { get; set; }
public Person()
{
awards = new List<Award>();
links = new List<Link>();
ID = PSID;
PSID++;
}
}
Result:
{"DOB":1234.5,"ID":1,"name":"John","nameToken":"token"}

How do I update a record that has a child entity?

I am trying to update a record and its child at the same time. When I create the object from the database the child property is null (the property is a generic list).
I want to update the class and also update the child class without creating duplicated records in the system.
Here is how I generate the object:
var r = db.SupplierAs.Where(o => o.id == 1).First();
The SupplierA class has a property List. Using the above line of code this comes back null. I have been trying work out the code to initialize this property so I can update it but I am having no joy.
This is the original item I created:
db.Products.Add(new Product
{
name = "product test",
supplierA = new SupplierA
{
name = "supA",
price = 1.99m,
sku = "abc123",
otherCurrencies = new List<Currency>
{
new Currency
{
eur = 2.99m,
usd = 3.99m
}
}
},
});
db.SaveChanges();
I can update the supplier on its own easily like so:
var r = db.SupplierAs.Where(o => o.id == 1).First();
r.name = "Updated name";
db.SupplierAs.Attach(r);
db.Entry(r).State = EntityState.Modified;
db.SaveChanges();
But I cannot figure out how to generate the Currency object list as part of the SupplierAs object. Currencies doesnt seem to be in the db context.
Here are the class files:
public class Product
{
public int id { get; set; }
public string name { get; set; }
public virtual SupplierA supplierA { get; set; }
}
public class SupplierA
{
public int id { get; set; }
public string name { get; set; }
public string sku { get; set; }
public decimal price { get; set; }
public List<Currency> Currencies { get; set; }
}
public class Currency
{
public int id { get; set; }
public decimal eur { get; set; }
public decimal usd { get; set; }
}
The idea of products, suppliers and currencies doesn't make the greatest sense I know, I have extracted logic from my app in example, hopefully it makes enough sense what I am trying to achieve.

How can I tell what subclass was saved?

I have a model with 2 subclasses:
public class User
{
public string eRaiderUsername { get; set; }
public int AllowedSpaces { get; set; }
public ContactInformation ContactInformation { get; set; }
public Ethnicity Ethnicity { get; set; }
public Classification Classification { get; set; }
public Living Living { get; set; }
}
public class Student : User
{
public Student()
{
AllowedSpaces = AppSettings.AllowedStudentSpaces;
}
}
public class OrganizationRepresentative : User
{
public Organization Organization { get; set; }
public OrganizationRepresentative()
{
AllowedSpaces = AppSettings.AllowedOrganizationSpaces;
}
}
public class ContactInformation
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string CellPhoneNumber { get; set; }
}
public enum Ethnicity
{
AfricanAmerican,
AmericanIndian,
Asian,
Black,
Hispanic,
NativeHawaiian,
NonResidentAlien,
White
}
public enum Classification
{
Freshman,
Sophomore,
Junior,
Senior,
GraduateStudent
}
public enum Living
{
OnCompus,
OffCampus
}
This is (mostly) saving fine using these initializers:
var students = new List<Student>
{
new Student{ eRaiderUsername="somestudent", ContactInformation=new ContactInformation{FirstName="Some", LastName="Student", EmailAddress="student#example.com", CellPhoneNumber="1234567890"}, Classification=Classification.Junior, Ethnicity=Ethnicity.Hispanic, Living=Living.OffCampus }
};
students.ForEach(s => context.Users.Add(s));
context.SaveChanges();
var orgReps = new List<OrganizationRepresentative>
{
new OrganizationRepresentative{ eRaiderUsername="somerep", ContactInformation=new ContactInformation{FirstName="Some", LastName="Representative", EmailAddress="orgrep#example.com", CellPhoneNumber="0987654321"}, Classification=Classification.Freshman, Ethnicity=Ethnicity.White, Living=Living.OnCompus, Organization=context.Organizations.Find(1) }
};
orgReps.ForEach(o => context.Users.Add(o));
context.SaveChanges();
None of the enums are saving (advice on this would be awesome too). But everything else is saving fine.
I have noticed Entity has added a Discriminator column with the subclass names. How do I use this to query only students, only organization reps, or just tell if the current object is a student or organization rep in a controller or view?
The discriminator column is used internally by EF to determine the type of object to instantiate.
For example you could query for a student directly. context.Set<Student>.Find(id). The same is true for an org rep. Or you could query for any user context.Set<User>.Find(id).
If you query for a student, but pass an org rep's ID, then EF will return null, because the ID doesn't belong to a student.

Update mongo document filtered by parent and nested Id using C# driver

First, sorry for my english ;)
I have the next classes that represents a person which can have many email accounts and each of those accounts have its respective email messages
public class Person
{
public string Id { get; set; }
public string Name { get; set; }
public EmailAccount[] Accounts { get; set; }
}
public class EmailAccount
{
public string Id { get; set; }
public string Name { get; set; }
public Message[] Messages { get; set; }
}
public class Message
{
public string Date { get; set; }
public string Content { get; set; }
}
I need to replace the messages of particular email account, but i need first do a select by person id and account id, how can i do this with Mongodb c# driver, I made something like this but without success:
var collection = _mongoDatabase.GetCollection<Person>("Person");
var query = Query.EQ("Person._id", "20");
var bsonMessages = BsonDocumentWrapper.CreateMultiple(new[]
{
new Message(DateTime.Now.ToString(CultureInfo.InvariantCulture), "Test 55"),
new Message(DateTime.Now.ToString(CultureInfo.InvariantCulture), "Test 66")
});
var messageArray = new BsonArray(bsonMessages);
var update = Update.Set("Accounts.$.Messages", messageArray);
collection.Update(query, update, UpdateFlags.Upsert);
As you can see, I do the filter the person by its Id but I don't know how to add the second filter to select an account by its Id
I appreciate any help ;)

Categories