How can I match a specific existing object by a string property? - c#

I am trying to make a query system for objects, but I have been unable to figure out how to turn a string from Console.ReadLine() to an object's name. Here is an example code snippet:
class Program
{
Notes note = new Notes();
note.notes = "note";
Notes note2 = new Notes();
note2.notes = "note2";
Console.WriteLine("which note would you like?");
string which = Console.ReadLine();
if(which.ToUpper == ("NOTE" || "NOTE2")
{
Console.WriteLine(which + "\'s note is " + which.note);//this is where I need to find an object from a string
}else
{
Console.WriteLine("There is no note called " + which);
}
}
class Notes
{
string notes;
}
For context, I intend to add a lot more objects into this and a large amount of if statements to find the right object will not be very practical
If I need to be more clear, please say so. Thanks for any help you can give.

since there can be many notes, you can use list to search
Notes note = new Notes();
note.notes = "note";
Notes note2 = new Notes();
note2.notes = "note2";
var noteList = new List<Notes>();
noteList.Add(note);
noteList.Add(note2);
Notes foundNote = null
foreach(var note in noteList)
{
if (note.notes.Equals(which, StringComparison.OrdinalIgnoreCase))
{
foundNote = note;
break;
}
}
if (foundNote != null)
Console.WriteLine("the note is {0}", foundNote.Notes);
else
Console.WriteLine("no notes");

you have two choices either you can choose dictionary or use the reflection approach.
solution with dictionary.
Dictionary<string, Notes> dict = new Dictionary<string, Notes>();
Notes note = new Notes();
note.notes = "note";
dict["note"] = note;
Notes selectedNote = dict[which];
Console.WriteLine($"{which}'s note is {selectedNote.notes}");
solution with reflection.
FieldInfo field = typeof(Program).GetField(which, BindingFlags.NonPublic);
if (field != null)
{
object selectedNote = field.GetValue(null);
Console.WriteLine($"{which}'s note is {((Notes)selectedNote).notes}");
}

Related

C# MVC Loop through list and update each record efficiently

I have a list of 'Sites' that are stored in my database. The list is VERY big and contains around 50,000+ records.
I am trying to loop through each record and update it. This takes ages, is there a better more efficient way of doing this?
using (IRISInSiteLiveEntities DB = new IRISInSiteLiveEntities())
{
var allsites = DB.Sites.ToList();
foreach( var sitedata in allsites)
{
var siterecord = DB.Sites.Find(sitedata.Id);
siterecord.CabinOOB = "Test";
siterecord.TowerOOB = "Test";
siterecord.ManagedOOB = "Test";
siterecord.IssueDescription = "Test";
siterecord.TargetResolutionDate = "Test";
DB.Entry(siterecord).State = EntityState.Modified;
}
DB.SaveChanges();
}
I have cut the stuff out of the code to get to the point. The proper function code I am using basically pulls a list out from Excel, then matches the records in the sites list and updates each record that matches accordingly. The DB.Find is slowing the loop down dramatically.
[HttpPost]
public ActionResult UploadUpdateOOBList()
{
CheckPermissions("UpdateOOBList");
string[] typesallowed = new string[] { ".xls", ".xlsx" };
HttpPostedFileBase file = Request.Files[0];
var fname = file.FileName;
if (!typesallowed.Any(fname.Contains))
{
return Json("NotAllowed");
}
file.SaveAs(Server.MapPath("~/Uploads/OOB List/") + fname);
//Create empty OOB data list
List<OOBList.OOBDetails> oob_data = new List<OOBList.OOBDetails>();
//Using ClosedXML rather than Interop Excel....
//Interop Excel: 30 seconds for 750 rows
//ClosedXML: 3 seconds for 750 rows
string fileName = Server.MapPath("~/Uploads/OOB List/") + fname;
using (var excelWorkbook = new XLWorkbook(fileName))
{
var nonEmptyDataRows = excelWorkbook.Worksheet(2).RowsUsed();
foreach (var dataRow in nonEmptyDataRows)
{
//for row number check
if (dataRow.RowNumber() >= 4 )
{
string siteno = dataRow.Cell(1).GetValue<string>();
string sitename = dataRow.Cell(2).GetValue<string>();
string description = dataRow.Cell(4).GetValue<string>();
string cabinoob = dataRow.Cell(5).GetValue<string>();
string toweroob = dataRow.Cell(6).GetValue<string>();
string manageoob = dataRow.Cell(7).GetValue<string>();
string resolutiondate = dataRow.Cell(8).GetValue<string>();
string resolutiondate_converted = resolutiondate.Substring(resolutiondate.Length - 9);
oob_data.Add(new OOBList.OOBDetails
{
SiteNo = siteno,
SiteName = sitename,
Description = description,
CabinOOB = cabinoob,
TowerOOB = toweroob,
ManageOOB = manageoob,
TargetResolutionDate = resolutiondate_converted
});
}
}
}
//Now delete file.
System.IO.File.Delete(Server.MapPath("~/Uploads/OOB List/") + fname);
Debug.Write("DOWNLOADING LIST ETC....\n");
using (IRISInSiteLiveEntities DB = new IRISInSiteLiveEntities())
{
var allsites = DB.Sites.ToList();
//Loop through sites and the OOB list and if they match then tell us
foreach( var oobdata in oob_data)
{
foreach( var sitedata in allsites)
{
var indexof = sitedata.SiteName.IndexOf(' ');
if( indexof > 0 )
{
var OOBNo = oobdata.SiteNo;
var OOBName = oobdata.SiteName;
var SiteNo = sitedata.SiteName;
var split = SiteNo.Substring(0, indexof);
if (OOBNo == split && SiteNo.Contains(OOBName) )
{
var siterecord = DB.Sites.Find(sitedata.Id);
siterecord.CabinOOB = oobdata.CabinOOB;
siterecord.TowerOOB = oobdata.TowerOOB;
siterecord.ManagedOOB = oobdata.ManageOOB;
siterecord.IssueDescription = oobdata.Description;
siterecord.TargetResolutionDate = oobdata.TargetResolutionDate;
DB.Entry(siterecord).State = EntityState.Modified;
Debug.Write("Updated Site ID/Name Record: " + sitedata.Id + "/" + sitedata.SiteName);
}
}
}
}
DB.SaveChanges();
}
var nowdate = DateTime.Now.ToString("dd/MM/yyyy");
System.IO.File.WriteAllText(Server.MapPath("~/Uploads/OOB List/lastupdated.txt"),nowdate);
return Json("Success");
}
Looks like you are using Entity Framework (6 or Core). In either case both
var siterecord = DB.Sites.Find(sitedata.Id);
and
DB.Entry(siterecord).State = EntityState.Modified;
are redundant, because the siteData variable is coming from
var allsites = DB.Sites.ToList();
This not only loads the whole Site table in memory, but also EF change tracker keeps reference to every object from that list. You can easily verify that with
var siterecord = DB.Sites.Find(sitedata.Id);
Debug.Assert(siterecord == sitedata);
The Find (when the data is already in memory) and Entry methods themselves are fast. But the problem is that they by default trigger automatic DetectChanges, which leads to quadratic time complexity - in simple words, very slow.
With that being said, simply remove them:
if (OOBNo == split && SiteNo.Contains(OOBName))
{
sitedata.CabinOOB = oobdata.CabinOOB;
sitedata.TowerOOB = oobdata.TowerOOB;
sitedata.ManagedOOB = oobdata.ManageOOB;
sitedata.IssueDescription = oobdata.Description;
sitedata.TargetResolutionDate = oobdata.TargetResolutionDate;
Debug.Write("Updated Site ID/Name Record: " + sitedata.Id + "/" + sitedata.SiteName);
}
This way EF will detect changes just once (before SaveChanges) and also will update only the modified record fields.
I have followed Ivan Stoev's suggestion and have changed the code by removing the DB.Find and the EntitySate Modified - It now takes about a minute and a half compared to 15 minutes beforehand. Very suprising as I didn't know that you dont actually require that to update the records. Clever. The code is now:
using (IRISInSiteLiveEntities DB = new IRISInSiteLiveEntities())
{
var allsites = DB.Sites.ToList();
Debug.Write("Starting Site Update loop...");
//Loop through sites and the OOB list and if they match then tell us
//750 records takes around 15-20 minutes.
foreach( var oobdata in oob_data)
{
foreach( var sitedata in allsites)
{
var indexof = sitedata.SiteName.IndexOf(' ');
if( indexof > 0 )
{
var OOBNo = oobdata.SiteNo;
var OOBName = oobdata.SiteName;
var SiteNo = sitedata.SiteName;
var split = SiteNo.Substring(0, indexof);
if (OOBNo == split && SiteNo.Contains(OOBName) )
{
sitedata.CabinOOB = oobdata.CabinOOB;
sitedata.TowerOOB = oobdata.TowerOOB;
sitedata.ManagedOOB = oobdata.ManageOOB;
sitedata.IssueDescription = oobdata.Description;
sitedata.TargetResolutionDate = oobdata.TargetResolutionDate;
Debug.Write("Thank you, next: " + sitedata.Id + "\n");
}
}
}
}
DB.SaveChanges();
}
So first of all you should turn your HTTPPost in an async function
more info https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
What you then should do is create the tasks and add them to a list. Then wait for them to complete (if you want/need to) by calling Task.WaitAll()
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.waitall?view=netframework-4.7.2
This will allow your code to run in parallel on multiple threads optimizing performance quite a bit already.
You can also use linq to for example reduce the size of allsites beforehand by doing something that will roughly look like this
var sitedataWithCorrectNames = allsites.Where(x => x //evaluate your condition here)
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/supported-and-unsupported-linq-methods-linq-to-entities
and then start you foreach (var oobdata) with the now foreach(sitedate in sitedataWithCorrectNames)
Same goes for SiteNo.Contains(OOBName)
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/getting-started-with-linq
P.S. Most db sdk's also provide asynchornous functions so use those aswell.
P.P.S. I didn't have an IDE so I eyeballed the code but the links should provide you with plenty of samples. Reply if you need more help.

Array to List<t> c#

My current code works and output is correct. I am pulling data from a data.txt file and have successfully done so to an array using TextFieldParser. Is there a way to convert my code to a List? And how so? If converting is not an option then any recommendations on where to start with the code? Basically trying to go from an array to a list collections.
public partial class EmployeeInfoGeneratorForm : Form
{
public EmployeeInfoGeneratorForm()
{
InitializeComponent();
}
// button event handler
private void GenerateButton_Click(object sender, EventArgs e)
{
string[] parts;
if(File.Exists("..\\data.txt"))
{
TextFieldParser parser = new TextFieldParser("..\\data.txt");
parser.Delimiters = new string[] { "," };
while (true)
{
parts = parser.ReadFields();
if (parts == null)
{
break;
}
this.nameheadtxt.Text = parts[0];
this.addressheadtxt.Text = parts[1];
this.ageheadtxt.Text = parts[2];
this.payheadtxt.Text = parts[3];
this.idheadtxt.Text = parts[4];
this.devtypeheadtxt.Text = parts[5];
this.taxheadtxt.Text = parts[6];
this.emp1nametxt.Text = parts[7];
this.emp1addresstxt.Text = parts[8];
this.emp1agetxt.Text = parts[9];
this.emp1paytxt.Text = parts[10];
this.emp1idtxt.Text = parts[11];
this.emp1typetxt.Text = parts[12];
this.emp1taxtxt.Text = parts[13];
this.emp2nametxt.Text = parts[14];
this.emp2addresstxt.Text = parts[15];
this.emp2agetxt.Text = parts[16];
this.emp2paytxt.Text = parts[17];
this.emp2idtxt.Text = parts[18];
this.emp2typetxt.Text = parts[19];
this.emp2taxtxt.Text = parts[20];
this.emp3nametxt.Text = parts[21];
this.emp3addresstxt.Text = parts[22];
this.emp3agetxt.Text = parts[23];
this.emp3paytxt.Text = parts[24];
this.emp3idtxt.Text = parts[25];
this.emp3typetxt.Text = parts[26];
this.emp3taxtxt.Text = parts[27];
}
}
else //Error Message for if File isn't found
{
lblError.Text = "File Not Found";
}
}
}
In your code example there are two arrays.
First example
parser.Delimiters = new string[] { "," };
Since parser is a TextFieldParser, I can see that Delimiters must be set to a string array. So you cannot change it.
Second example
string[] parts;
parts = parser.ReadFields();
This array accepts the result of parser.ReadFields(). The output of that function is a string array, so this code can't be changed without breaking the call.
However, you can immediately convert it to a list afterward:
var parts = parser.ReadFields().ToList();
There isn't much point to this either.
An array is just as good as a list when the size of the array/list doesn't change after it is created. Making it into a list will just add overhead.
There are a number of problems here. I'd be inclined to write your code like this:
public static IEnumerable<List<string>> ParseFields(string file)
{
// Use "using" to clean up the parser.
using (var parser = new TextFieldParser(file))
{
parser.Delimiters = new string[] { "," };
// Use end-of-data, not checks for null.
while (!parser.EndOfData)
yield return parser.ReadFields().ToList();
}
}
I'd refactor your code to put the UI updates in one method:
private void UpdateText(List<string> parts ) { ... }
You only do something with the last element in the sequence; all your previous edits are lost. So be explicit about that:
private void GenerateButton_Click(object sender, EventArgs e)
{
// Use a named constant for constant strings used in several places
const string data = "..\\data.txt";
if(!File.Exists(data))
{
lblError.Text = "File Not Found";
} else {
var parts = ParseFields(data).LastOrDefault();
if (parts != null)
UpdateText(parts);
}
}
See how much cleaner that logic looks when you break it up into smaller parts? It's very pleasant to have methods that fit easily onto a page.
A direct answer to your question:
Use the List<T> constructor that takes an IEnumerable<T> parameter.
With that said, I would read Mr. Lippert's answer until you fully understand it.

Dynamically create an object from list of properties known at run-time

I have results (xml format, <Prop0>, <Prop1>, ...) coming from an API that I am saving into my database table. I have a mapping of the Props to my table columns. I would like to create an object on the fly with property names coming from the mapping and values coming from the API results.
I could use the mapping dictionary and the API results to directly save the values in the database, but all the insert functions (remote to my application) are designed to use List of objects.
I have explored System.Dynamic.ExpandoObject but one cannot add properties on the fly to it (from, say, a dictionary). Currently, I am using the following generic but strongly typed solution, for which all the attributes need to be present in the class.
internal T FetchPopulatedObject<T>(Dictionary<string, string> apiToTableMapping, Dictionary<string, string> apiResultsKeyVal, T objectToPopulate) where T : new()
{
if (objectToPopulate == null)
objectToPopulate = new T();
PropertyInfo currProp;
Type currActualType;
string valToSet = string.Empty;
int valToSetNumeric = 0;
object finalValToSet = new object();
foreach (KeyValuePair<string, string> currMap in apiToTableMapping)
{
valToSet = string.Empty;
currProp = (typeof(T).GetProperty(currMap.Value));
if (currProp == null)
{
log.Info("Could not find property for table column " + currMap.Value + ". Moving on.");
continue;
}
bool valResult = apiResultsKeyVal.TryGetValue(currMap.Key, out valToSet);
if (!valResult)
{
log.Info("Could not find value in API Results for property: " + currMap.Key + ".");
continue;
}
currActualType = Nullable.GetUnderlyingType(currProp.PropertyType) ?? currProp.PropertyType;
if (string.IsNullOrEmpty(valToSet))
{
log.Info("Property " + currProp.Name + " is of " + currProp.PropertyType.Name + " type but API attribute " +
currMap.Key + " returned an incompatible or empty value " + valToSet + ". Skipping this field.");
continue;
}
else
{
finalValToSet = Int32.TryParse(valToSet, out valToSetNumeric) ? Convert.ChangeType(valToSetNumeric, currActualType) : Convert.ChangeType(valToSet, currActualType);
}
currProp.SetValue(objectToPopulate, finalValToSet, null);
}
return objectToPopulate;
}
However, this is infeasible, since the API results keep changing and adding new attributes.
Can somebody suggest how to accomplish the same without having to have all the properties already in class T?
Other than Emit, I can only think of a dynamic compiled type, example:
static Type GetMeAType( string myTypeName, string myStringPropertyName ) {
string codeToCompile = "using System; public class " + myTypeName + " {public string " +
myStringPropertyName + " { get; set; } }";
string[] references = { "System.dll", "System.Core.dll" };
CompilerParameters compilerParams = new CompilerParameters();
compilerParams.GenerateInMemory = true;
compilerParams.TreatWarningsAsErrors = false;
compilerParams.GenerateExecutable = false;
compilerParams.CompilerOptions = "/optimize";
compilerParams.ReferencedAssemblies.AddRange( references );
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerResults compile = provider.CompileAssemblyFromSource( compilerParams, codeToCompile );
Module dynamicModule = compile.CompiledAssembly.GetModules()[ 0 ];
var type = dynamicModule.GetType( myTypeName );
return type;
}
The only way to dynamically create a type in C# is by using the System.Reflection.Emit namespace. You cannot use a generic in this case, because even though the type T is not specified, it still implies type-safety, which requires a type to be created during compile-time.

Programmatic filtering RadGridView

Let's say that in my db, in a column named UserName, i have this value Jean-Andrew Pierre. I have a problem with creating the filter for retrieving this value from db, when i'm typing Andrew Jean Pierre. As you see, the order of names, are different and some characters are missing.
I've tried this:
var operatorNameFilter = radGridView.Columns[nameof(Username)].ColumnFilterDescriptor;
if (string.IsNullOrEmpty(username))
{
operatorNameFilter.Clear();
return;
}
allOperatorsName = Regex.Split(username, #"\W+");
foreach (var name in allOperatorsName)
operatorNameFilter.DistinctFilter.AddDistinctValue(name);
operatorNameFilter.FieldFilter.Filter1.Operator = FilterOperator.Contains;
operatorNameFilter.FieldFilter.Filter1.LogicalOperator = FilterCompositionLogicalOperator.And;
operatorNameFilter.FieldFilter.Filter1.Value = username;
I believe the last line has to be removed, but with or without, nothing happen
Thanks,
Andreea
I found an answer after few more tries:
private void FilterByOperatorNames(RadGridView radGridView)
{
var operatorNameFilter = radGridView.Columns[nameof(Username)].ColumnFilterDescriptor;
if (string.IsNullOrEmpty(username))
{
operatorNameFilter.Clear();
return;
}
var allOperatorNames = Regex.Split(username, #"\W+");
var userNamesCompositeFilter = new CompositeFilterDescriptor();
foreach (var name in allOperatorNames)
{
var filterDescriptor = new FilterDescriptor(nameof(Username), FilterOperator.Contains, name);
userNamesCompositeFilter.FilterDescriptors.Add(filterDescriptor);
}
userNamesCompositeFilter.LogicalOperator = FilterCompositionLogicalOperator.And;
radGridView.FilterDescriptors.Add(userNamesCompositeFilter);
}
I hope to help others.

Flatten One-to-Many Relationship Using Dynamic LINQ

Is it possible to flatten a one-to-many relationship using dynamic LINQ?
For example, I might have a list of Users and the User class contains a list of many UserPreferences. The UserPreference class is essentially a name/value pair.
A user will define what types of user preferences are available for a group of users.
public class User
{
public string FirstName
{
get;
set;
}
public string LastName
{
get;
set;
}
public IList<UserPreference> UserPreferences
{
get;
set;
}
}
public class UserPreference
{
public UserPreference(string name, object userValue)
{
this.Name = name;
this.UserValue = userValue;
}
public string Name
{
get;
set;
}
public object UserValue
{
get;
set;
}
}
Therefore one user group might be defined in the following way:
List<User> users = new List<User>();
User user1 = new User();
user1.FirstName = "John";
user1.LastName = "Doe";
user1.UserPreferences.Add(new UserPreference("Favorite color", "Red"));
User user2 = new User();
user2.FirstName = "Jane";
user2.LastName = "Doe";
user2.UserPreferences.Add(new UserPreference("Favorite mammal", "Dolphin"));
user2.UserPreferences.Add(new UserPreference("Favorite color", "Blue"));
users.Add(user1);
users.Add(user2);
return users;
The desired output would be:
First Name Last Name Favorite Color Favorite Mammal
John Doe Red NULL
Jane Doe Blue Dolphin
Is there a way to create an anonymous type so that UserPreferences would get rolled up into the User?
For example,
var u = UserScopedSettingAttribute.Select("new (FirstName as FirstName, UserValue as FavoriteColor)", null);
string name = u.FirstName;
string color = u.FavoriteColor;
Ultimately this list of Users will get bound to an ASP.NET GridView web control. There will be a large volume of data involved in this operation and performance will be critical.
Any suggestions are appreciated!
I know it doesn't exactly answer your question, but compiling strings into new classes at runtime like dlinq does has always had kind of a bad smell to it. Consider just simply using a DataTable like this,
DataTable prefs = new DataTable();
IEnumerable<DataColumn> cols = (from u in users
from p in u.UserPreferences
select p.Name)
.Distinct()
.Select(n => new DataColumn(n));
prefs.Columns.Add("FirstName");
prefs.Columns.Add("LastName");
prefs.Columns.AddRange(cols.ToArray());
foreach (User user in users)
{
DataRow row = prefs.NewRow();
row["FirstName"] = user.FirstName;
row["LastName"] = user.LastName;
foreach (UserPreference pref in user.UserPreferences)
{
row[pref.Name] = pref.UserValue;
}
prefs.Rows.Add(row);
}
This should do it. Flattening is generally done with SelectMany extension method, but in this case I am using a let expression. The code to remove the null preferences is a bit ugly and could prob be improved but it works:
var flattenedUsers = from user in GetUsers()
let favColor = user.UserPreferences.FirstOrDefault(pref => pref.Name == "Favorite color")
let favMammal = user.UserPreferences.FirstOrDefault(pref => pref.Name == "Favorite mammal")
select new
{
user.FirstName,
user.LastName,
FavoriteColor = favColor == null ? "" : favColor.UserValue,
FavoriteMammal = favMammal == null ? "" : favMammal.UserValue,
};
My best suggestion would be to not use dynamic LINQ, but add a flatuser class and then loop through the users. The code for this is simple, and if you were able to get a linq query with similar results it would generate the same code, although you can't really tell how optimized it would be as it might involve some joins that would incur a performance penalty instead of just looping. If you were pulling this from a database using LINQ to SQL then you could use an entity relation to to get the data using linq instead of this loop.
Loop:
List<FlatUser> flatusers = new List<FlatUser>();
foreach (User u in users)
{
foreach (UserPreference up in u.UserPreferences)
{
flatusers.Add(new FlatUser
{
FirstName = u.FirstName,
LastName = u.LastName,
Name = up.Name,
UserValue = up.UserValue
});
}
}
Flat User Class:
public class FlatUser
{
public string FirstName
{
get;
set;
}
public string LastName
{
get;
set;
}
public string Name
{
get;
set;
}
public object UserValue
{
get;
set;
}
}
Unfortunately
var u = UserScopedSettingAttribute.Select("new {FirstName as FirstName, UserValue as FavoriteColor}", null);
string name = u.FirstName;
string color = u.FavoriteColor;
won't work. When you use DLINQ Select(string) the strongest compile time class information you have is Object, so u.FirstName will throw a compile error. The only way to pull the properties of the runtime generated anonymous class is to use reflection. Although, if you can wait, this will be possible with C# 4.0 like this,
dynamic u = UserScopedSettingAttribute.Select("new {FirstName as FirstName, UserValue as FavoriteColor}", null);
string name = u.FirstName;
string color = u.FavoriteColor;
I think the pragmatic answer here is to say your attempting to force C# to become a dynamic language and any solution is going to be really pushing C# to its limits. Sounds like your trying to transform a database query of columns that are only determined at query time into a collection that is based on those columns and determined at run time.
Linq and Gridview binding is really pretty and succinct and all but you have to start thinking about weighing the benefit of getting this compiler bending solution to work just so you don't have to dynamically generate gridview rows and columns by yourself.
Also if your concerned about performance I'd consider generating the raw HTML. Relying on the collection based WebForms controls to efficiently display large sets of data can get dicey.
You add in a couple of OnItemDataBound events and boxing and unboxing is going to really gum up the works. I'm assuming too your going to want to add interactive buttons and textboxes to the rows as well and doing 1000 FindControls has never been fast.
There are probably more efficient ways to do this, but to actually answer your question, I came up with the following code. (Note that I've never worked with DynamicLinq before, so there may be a better way to use it to accomplish your goal.)
I created a console application, pasted in the classes from your post, then used the following code.
static void Main(string[] args)
{
var users = GetUserGroup();
var rows = users.SelectMany(x => x.UserPreferences.Select(y => new { x.FirstName, x.LastName, y.Name, y.UserValue }));
var userProperties = rows.Select(x => x.Name).Distinct();
foreach (var property in userProperties)
{
Console.WriteLine(property);
}
Console.WriteLine();
// The hard-coded variety.
var results = users.Select(x => new
{
x.FirstName,
x.LastName,
FavoriteColor = x.UserPreferences.Where(y => y.Name == "Favorite color").Select(y => y.UserValue).FirstOrDefault(),
FavoriteAnimal = x.UserPreferences.Where(y => y.Name == "Favorite mammal").Select(y => y.UserValue).FirstOrDefault(),
});
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The dynamic variety.
DynamicProperty[] dynamicProperties = new DynamicProperty[2 + userProperties.Count()];
dynamicProperties[0] = new DynamicProperty("FirstName", typeof(string));
dynamicProperties[1] = new DynamicProperty("LastName", typeof(string));
int propIndex = 2;
foreach (var property in userProperties)
{
dynamicProperties[propIndex++] = new DynamicProperty(property, typeof(string));
}
Type resultType = ClassFactory.Instance.GetDynamicClass(dynamicProperties);
ConstructorInfo constructor = resultType.GetConstructor(new Type[] {});
object[] constructorParams = new object[] { };
PropertyInfo[] propInfoList = resultType.GetProperties();
PropertyInfo[] constantProps = propInfoList.Where(x => x.Name == "FirstName" || x.Name == "LastName").OrderBy(x => x.Name).ToArray();
IEnumerable<PropertyInfo> dynamicProps = propInfoList.Where(x => !constantProps.Contains(x));
// The actual dynamic results creation.
var dynamicResults = users.Select(user =>
{
object resultObject = constructor.Invoke(constructorParams);
constantProps[0].SetValue(resultObject, user.FirstName, null);
constantProps[1].SetValue(resultObject, user.LastName, null);
foreach (PropertyInfo propInfo in dynamicProps)
{
var val = user.UserPreferences.FirstOrDefault(x => x.Name == propInfo.Name);
if (val != null)
{
propInfo.SetValue(resultObject, val.UserValue, null);
}
}
return resultObject;
});
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Display the results.
var displayResults = dynamicResults;
//var displayResults = results;
if (displayResults.FirstOrDefault() != null)
{
PropertyInfo[] properties = displayResults.First().GetType().GetProperties();
int columnWidth = Console.WindowWidth / properties.Length;
int index = 0;
foreach (PropertyInfo property in properties)
{
Console.SetCursorPosition(index++ * columnWidth, Console.CursorTop);
Console.Write(property.Name);
}
Console.WriteLine();
foreach (var result in displayResults)
{
index = 0;
foreach (PropertyInfo property in properties)
{
Console.SetCursorPosition(index++ * columnWidth, Console.CursorTop);
Console.Write(property.GetValue(result, null) ?? "(null)");
}
Console.WriteLine();
}
}
Console.WriteLine("\r\nPress any key to continue...");
Console.ReadKey();
}
static List<User> GetUserGroup()
{
List<User> users = new List<User>();
User user1 = new User();
user1.FirstName = "John";
user1.LastName = "Doe";
user1.UserPreferences = new List<UserPreference>();
user1.UserPreferences.Add(new UserPreference("Favorite color", "Red"));
user1.UserPreferences.Add(new UserPreference("Birthday", "Friday"));
User user2 = new User();
user2.FirstName = "Jane";
user2.LastName = "Doe";
user2.UserPreferences = new List<UserPreference>();
user2.UserPreferences.Add(new UserPreference("Favorite mammal", "Dolphin"));
user2.UserPreferences.Add(new UserPreference("Favorite color", "Blue"));
users.Add(user1);
users.Add(user2);
return users;
}

Categories