I have a set of string that I need to store in a set, such as:
id, firstname, lastname, city, country, language
All of the above apply to a single person (represented by the ID)
Now I have 60 - 70 of these (and growing), how could I organize them? I have looked at the NameValueCollection class - and it does exactly what I want (if I only had two fields), but since I have 6 fields, I can't use it. E.g.:
public NameValueCollection personCollection = new NameValueCollection
{
{ "harry", "townsend", "london", "UK", "english" },
{ "john", "cowen", "liverpool", "UK", "english" },
// and so on...
};
Although this does not work :( Could someone suggest another way of achieving this?
how about you make a Person class with the attributes you need?
public class Person
{
public int id { get; set; }
public string firstname { get; set; }
public string lastname { get; set; }
// more attributes here
}
then, just instantiate the Person class and make new Person objects.
You can then add those Persons to a List.
Person somePerson = new Person();
somePerson.firstname = "John";
somePerson.lastname = "Doe";
somePerson.id = 1;
List<Person> listOfPersons = new List<Person>();
listOfPersons.Add(somePerson);
If you absolutely don’t want to create any new classes, you could use a dictionary of lists, keyed by your ID:
IDictionary<string, IList<string>> personCollection =
new Dictionary<string, IList<string>>
{
{ "1", new [] { "harry", "townsend", "london", "UK", "english" }},
{ "2", new [] { "john", "cowen", "liverpool", "UK", "english" }},
};
…which you could then access using dictionary and list indexers:
Console.WriteLine(personCollection["1"][0]); // Output: "harry"
Console.WriteLine(personCollection["2"][2]); // Output: "liverpool"
However, the correct OOP approach would be to define a class with properties for your respective strings:
public class Person
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string Country { get; set; }
public string Language { get; set; }
public Person() { }
public Person(string id, string firstName, string lastName,
string city, string country, string language)
{
this.Id = id;
this.FirstName = firstName;
this.LastName = lastName;
this.City = city;
this.Country = country;
this.Language = language;
}
}
You could then create a list of persons:
IList<Person> persons = new List<Person>()
{
new Person("1", "harry", "townsend", "london", "UK", "english"),
new Person("2", "john", "cowen", "liverpool", "UK", "english"),
};
Related
Need help with a better pratices question
I have an azure function that brings data form differents APIs and match them toguether to create a final csv report. I have a poblation of 60k-100k and 30 columns
For the sake of the explanation, I'm going to use a small School example.
public Student {
string Grade {get; set;}
Name LegName {get; set;}
string FatherName {get; set;}
string TeacherId {get; set;}
string SchoolId {get; set;}
}
public Name {
string FirstName {get; set;}
string LastName {get; set;}
}
Before constructing the report, I create two Dictionary with <Id, Name> from two APIs that expose Schools and Teachers information. And of course, a list of Student that comes from the Student APIs. I have no control of this trhee APIs, design, data quality, nothing.
Now, when I have all the data, I start to create the report.
string GenerateTXT(Dictionary<string, string> schools, Dictionary<string, string> teachers, Student students){
StringBuilder content = new StringBuilder();
foreach(var student in students){
content.Append($"{student.Grade}\t");
content.Append($"{student.LegName.FirstName}\t");
content.Append($"{student.LegName.LastName}\t");
content.Append($"{schools.TryGetValue(student.TeacherId)}\t");
content.Append($"{teachers.TryGetValue(student.SchoolId)}t";
content.Append($"{student.FatherNme}\t");
content.AppendLine();
}
return content.ToString();
}
Now here comes the problem. I started noticing data quality issues so the function started throwing exceptions. For example, students who do not have a valid school or teacher, or a student who does not have a name. I tried to solve expected scenarios and exception handling.
string GenerateTXT(Dictionary<string, string> schools, Dictionary<string, string> teachers, Student students){
StringBuilder content = new StringBuilder();
var value = string.Empty;
foreach(var student in students){
try {
content.Append($"{student.Grade}\t");
content.Append($"{student.LegName.FirstName}\t");
content.Append($"{student.LegName.LastName}\t");
if(teachers.TryGetValue(student.TeacherId))
content.Append($"{teachers[student.TeacherId]}\t");
else
content.Append($"\t");
if(schools.TryGetValue(student.SchoolId))
content.Append($"{schools[student.SchoolId]}\t");
else
content.Append($"\t");
content.Append($"{student.FatherNme}\t");
content.AppendLine();
}
catch(Exception ex) {
log.Error($"Error reading worker {student.FirstName}");
}
}
return content.ToString();
}
The problem with this is that when an unexpected error happens, I stop reading the next columns of data that maybe I have and instead jump to the next worker. Therefore, if a student for some random reason does not have a name, that row in the report will only have the grade, and nothing else, but I actually had the rest of the values. So here comes the question. I could put a try catch on each column, but remember that my real scenario has like 30 columns and could be more... so I think it's a really bad solution. Is there a pattern to solve this in a better way?
Thanks in advance!
So the first bit of advice I am going to give you is to use CsvHelper. This is a tried and true library as it handles all those edge cases you will never think of. So, saying that, give this a shot:
public class Student
{
public string Grade { get; set; }
public Name LegName { get; set; }
public string FatherName { get; set; }
public string TeacherId { get; set; }
public string SchoolId { get; set; }
}
public class Name
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class NormalizedData
{
public string Grade { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string School { get; set; }
public string Teacher { get; set; }
public string FatherName { get; set; }
}
static void GenerateCSVData(CsvHelper.CsvWriter csv, Dictionary<string, string> schools,
Dictionary<string, string> teachers, Student[] students)
{
var normalizedData = students.Select(x => new NormalizedData
{
Grade = x.Grade,
FatherName = x.FatherName,
FirstName = x.LegName?.FirstName, // sanity check incase LegName is null
LastName = x.LegName?.LastName, // ...
School = schools.ContainsKey(x.SchoolId ?? string.Empty) ? schools[x.SchoolId] : null,
Teacher = teachers.ContainsKey(x.TeacherId ?? string.Empty) ? teachers[x.TeacherId] : null
});
csv.WriteRecords(normalizedData);
}
private static string GenerateStringCSVData(Dictionary<string, string> schools,
Dictionary<string, string> teachers, Student[] students)
{
using(var ms = new MemoryStream())
{
using(var sr = new StreamWriter(ms, leaveOpen: true))
using (var csv = new CsvHelper.CsvWriter(sr,
new CsvConfiguration(CultureInfo.InvariantCulture)
{
Delimiter = ",", // change this to "\t" if you want to use tabs
Encoding = Encoding.UTF8
}))
{
GenerateCSVData(csv, schools, teachers, students);
}
ms.Position = 0;
return Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length);
}
}
private static int Main(string[] args)
{
var teachers = new Dictionary<string, string>
{
{ "j123", "Jimmy Carter" },
{ "r334", "Ronald Reagan" },
{ "g477", "George Bush" }
};
var schools = new Dictionary<string, string>
{
{ "s123", "Jimmy Carter University" },
{ "s334", "Ronald Reagan University" },
{ "s477", "George Bush University" }
};
var students = new Student[]
{
new Student
{
FatherName = "Bob Jimmy",
SchoolId = "s477",
Grade = "5",
LegName = new Name{ FirstName = "Apple", LastName = "Jimmy" },
TeacherId = "r334"
},
new Student
{
FatherName = "Jim Bobby",
SchoolId = null, // intentional
Grade = "", // intentional
LegName = null, // intentional
TeacherId = "invalid id" // intentional
},
new Student
{
FatherName = "Mike Michael",
SchoolId = "s123",
Grade = "12",
LegName = new Name{ FirstName = "Peach", LastName = "Michael" },
TeacherId = "g477"
},
};
var stringData = GenerateStringCSVData(schools, teachers, students);
return 0;
}
This outputs:
Grade,FirstName,LastName,School,Teacher,FatherName
5,Apple,Jimmy,George Bush University,Ronald Reagan,Bob Jimmy
,,,,,Jim Bobby
12,Peach,Michael,Jimmy Carter University,George Bush,Mike Michael
So, you can see, one of the students has invalid data in it, but it recovers just fine by placing blank data instead of crashing or throwing exceptions.
Now I haven't seen your original data, so there may be more tweaks you have to make to this to cover all edge cases, but it will be a lot easier to tweak this when using CsvHelper as your writer.
I have an object Person like this:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
}
I tried to create a list like this:
var personList = new List<object>();
personList.Add(new Person { Address = "addr1", Age = 20, Name = "Person1" });
personList.Add(new Person { Address = "addr2", Age = 22, Name = "Person2" });
personList.Add(new Person { Address = "addr3", Age = 25, Name = "Person1" });
var jsonString = JsonConvert.SerializeObject(personList);
This is the result of jsonString
[{"Name":"Person1","Age":20,"Address":"addr1"},
{"Name":"Person2","Age":22,"Address":"addr2"},
{"Name":"Person1","Age":25,"Address":"addr3"}]
Below is my expected result, so how can I do that?
{
Person1:{"Name":"Person1","Age":20,"Address":"addr1"},
Person2:{"Name":"Person2","Age":22,"Address":"addr2"},
Person3:{"Name":"Person3","Age":25,"Address":"addr3"}
}
You have to use a Dictionary<string, Person> when I am not that wrong.
This should serialize it that way, you want it.
You can add the Attribute [JsonIgnore] to Name optionally, if you don't want redundant data.
Edit: You can serialize a list directly to a dictionary by using a custom JsonConverter: Newtonsoft.Json serialize collection (with indexer) as dictionary
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
// constructor
public Person(string name, int age, string address){
this.Name = name;
this.Age = age;
this.Address = address;
}
}
then
List<person>People = new List<person>();
People.Add(new Person("name1", 1, "address1"));
People.Add(new Person("name2", 2, "address2"));
People.Add(new Person("name3", 3, "address3"));
string jsonString = JsonConvert.SerializeObject(People);
then on the client, omitting the part about getting it to the client
var people = JSON.parse(jsonString)
I'd like to merge one object into another in a generic way that can be used repeatedly for other more complex objects. I only want the NULLs to change.
ie. merge sourcePerson and detinationPerson to get resultingPerson (below)
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int? Age { get; set; }
public string Address { get; set; }
}
var sourcePerson = new Person
{
FirstName = "Bill",
LastName = "Smith",
Age = 43,
Address = "123 Some Street"
};
var destinationPerson = new Person
{
FirstName = "Barbara",
LastName = null,
Age = 41,
Address = null
};
var resultingPerson = new Person
{
FirstName = "Barbara",
LastName = "Smith",
Age = 41,
Address = "123 Some Street"
};
I've tried Automapper, but can't seem to configure it properly. I feel like the following should work. Help?
Mapper.CreateMap<Person, Person>().ForAllMembers(opt => opt.UseDestinationValue());
Mapper.Map(sourcePerson, destinationPerson);
Thanks IronGeek and tutok.
Yes, the following works:
Mapper.CreateMap<Person, Person>()
.ForAllMembers(opt => opt.Condition(person => person.DestinationValue == null));
Your can use reflection like this:
public static T Merge<T>(T source, T destination)
{
var returnvalue = (T) Activator.CreateInstance(typeof (T));
foreach (var field in destination.GetType().GetProperties())
{
field.SetValue(returnvalue,
field.GetValue(destination, null) == null ? field.GetValue(source) : field.GetValue(destination));
}
return returnvalue;
}
I haven't tested this for other than simple DTOs.
I have the code below. I'd like to convert all items in this list to uppercase.
Is there a way to do this in Linq ?
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
public class MyClass
{
List<Person> myList = new List<Person>{
new Person { FirstName = "Aaa", LastName = "BBB", Age = 2 },
new Person{ FirstName = "Deé", LastName = "ève", Age = 3 }
};
}
Update
I don't want to loop or go field by field. Is there a way by reflection to uppercase the value for each property?
Why would you like to use LINQ?
Use List<T>.ForEach:
myList.ForEach(z =>
{
z.FirstName = z.FirstName.ToUpper();
z.LastName = z.LastName.ToUpper();
});
EDIT: no idea why you want to do this by reflection (I wouldn't do this personally...), but here's some code that'll uppercase all properties that return a string. Do note that it's far from being perfect, but it's a base for you in case you really want to use reflection...:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
public static class MyHelper
{
public static void UppercaseClassFields<T>(T theInstance)
{
if (theInstance == null)
{
throw new ArgumentNullException();
}
foreach (var property in theInstance.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
var theValue = property.GetValue(theInstance, null);
if (theValue is string)
{
property.SetValue(theInstance, ((string)theValue).ToUpper(), null);
}
}
}
public static void UppercaseClassFields<T>(IEnumerable<T> theInstance)
{
if (theInstance == null)
{
throw new ArgumentNullException();
}
foreach (var theItem in theInstance)
{
UppercaseClassFields(theItem);
}
}
}
public class Program
{
private static void Main(string[] args)
{
List<Person> myList = new List<Person>{
new Person { FirstName = "Aaa", LastName = "BBB", Age = 2 },
new Person{ FirstName = "Deé", LastName = "ève", Age = 3 }
};
MyHelper.UppercaseClassFields<Person>(myList);
Console.ReadLine();
}
}
LINQ does not provide any facilities to update underlying data. Using LINQ, you can create a new list from an existing one:
// I would say this is overkill since creates a new object instances and
// does ToList()
var updatedItems = myList.Select(p => new Person
{
FirstName = p.FirstName.ToUpper(),
LastName = p.LastName.ToUpper(),
Age = p.Age
})
.ToList();
If using LINQ is not principal, I would suggest using a foreach loop.
UPDATE:
Why you need such solution? Only one way of doing this in generic manner - reflection.
the Easiest approach will be to use ConvertAll:
myList = myList.ConvertAll(d => d.ToUpper());
Not too much different than ForEach loops the original list whereas ConvertAll creates a new one which you need to reassign.
var people = new List<Person> {
new Person { FirstName = "Aaa", LastName = "BBB", Age = 2 },
new Person{ FirstName = "Deé", LastName = "ève", Age = 3 }
};
people = people.ConvertAll(m => new Person
{
FirstName = m.FirstName?.ToUpper(),
LastName = m.LastName?.ToUpper(),
Age = m.Age
});
to answer your update
I don't want to loop or go field by field. Is there a way by
reflection to uppercase the value for each property?
if you don't want to loop or go field by field.
you could use property on the class to give you the Uppercase like so
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string FirstNameUpperCase => FirstName.ToUpper();
public string LastNameUpperCase => LastName.ToUpper();
}
or you could use back field like so
public class Person
{
private string _firstName;
public string FirstName {
get => _firstName.ToUpper();
set => _firstName = value;
}
private string _lastName;
public string LastName {
get => _lastName.ToUpper();
set => _lastName = value;
}
public int Age { get; set; }
}
You can only really use linq to provide a list of new objects
var upperList = myList.Select(p=> new Person {
FirstName = (p.FirstName == null) ? null : p.FirstName.ToUpper(),
LastName = (p.LastName == null) ? null : p.LastName.ToUpper(),
Age = p.Age
}).ToList();
p.lastname.ToString().ToUpper().Contains(TextString)
My entities are like this:
class Address
{
public string Number { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Address PostalAddress { get; set; }
}
Person newPerson =
new Person()
{
Name = "Kushan",
Age = 25,
PostalAddress =
new Address()
{
Number = "No 25",
Street = "Main Street",
City = "Matale",
Country = "Sri Lanka"
}
};
Now I wanna map this newPerson object into JSON object like this,
{
"PER_NAME" : "Kushan",
"PER_AGE" : "25",
"PER_ADDRESS" : {
"ADD_NUMBER" : "No 25",
"ADD_STREET" : "Main Street",
"ADD_CITY" : "Matale",
"ADD_COUNTRY" : "Sri Lanka"
}
}
Note: Above is just an example.
What I need is, I need to customize the Key at the serializing time. by default it is taking property name as the key. I can't change property names. How to do this?
Also, is it possible to change to order of appearing key-value pairs in JSON obj.?
You need to add DataContract attributes to your classes and DataMember to the properties. Set Name property of DataMemeber attribute to your custom property name and Order property to define the order.
[DataContract]
public class Person
{
[DataMember(Name = "PER_NAME", Order = 1)]
public string Name { get; set; }
[DataMember(Name = "PER_AGE", Order = 2)]
public int Age { get; set; }
[DataMember(Name = "PER_ADDRESS", Order = 3)]
public Address PostalAddress { get; set; }
}
Then you can do this:
var newPerson = new Person()
{
Name = "Kushan",
Age = 25,
PostalAddress = new Address()
{
Number = "No 25",
Street = "Main Street",
City = "Matale",
Country = "Sri Lanka"
}
};
MemoryStream stream = new MemoryStream();
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Person));
ser.WriteObject(stream, newPerson);
To check the result:
var result = Encoding.ASCII.GetString(stream.ToArray());
{"PER_NAME":"Kushan","PER_AGE":25,"PER_ADDRESS":{"ADD_NUMBER":"No 25","ADD_STREET":"Main Street","ADD_CITY":"Matale","ADD_COUNTRY":"Sri Lanka"}}
You can serialize an anonymous type with JavaScriptSerializer, so you might try projecting your object into the shape you want to serialize:
person.Select(s => new { PER_NAME = s.Name, PER_AGE = s.Age });