Get fields from anonymous type - c#

I have this class:
public class allFields
{
public string EAN { get; set; }
public string title { get; set; }
public string qty { get; set; }
public string price { get; set; }
public DateTime date { get; set; }
}
And a function that return an anonymous type:
public IEnumerable<object> stockEtatQty()
{
List<allFields> afList = new List<allFields>();
var query = from x in ctx.book
where x.qty > 0
select x;
foreach (var item in query)
{
allFields af = new allFields();
af.EAN = item.EAN;
af.title = item.Titre;
af.qty = ""+item.Quantite;
afList.Add(af);
}
var q = from x in afList
select new { EAN=x.EAN, Title=x.title, Quantity=x.qty };
return q; //q is a IEnumerable<'a> where a is new {string EAN, string Title, string Quantity}
}
In my WinForm a use this function as below:
private void QuantityToolStripMenuItem_Click(object sender, EventArgs e)
{
ServiceStock sstock = new ServiceStock();
var q = sstock.stockEtatQty().ToList();// q is a list<object>
string str = "";
foreach (var item in q)
{
str += item + Environment.NewLine;
}
MessageBox.Show(str);
}
The result is:
{ EAN = 1, Title = CSharp Security, Quantity = 970 }
{ EAN = 2, Title = MISC, Quantity = 100 }
...
What I want?
I want not like the result above, but separate each field apart of the item in the loop foreach, e.g get item.EAN, item.Title and item.Quantity.
If there is no solution for my problem I would like to know an alternative,
Thanks for help.

The obvious solution is to create a custom type (let's call it BookInfo) and return a IEnumerable<BookInfo> instead of a IEnumerable<object> (and maybe override ToString if you want to put the formatting into this class itself).
Then you can easily format the output.
public class BookInfo
{
public string EAN {get;set;}
public string Title {get;set;}
public int Quantity {get;set;}
}
public IEnumerable<BookInfo> stockEtatQty()
{
...
var q = from x in afList
select new BookInfo { EAN=x.EAN, Title=x.title, Quantity=x.qty };
return q;
}
private void QuantityToolStripMenuItem_Click(object sender, EventArgs e)
{
ServiceStock sstock = new ServiceStock();
var q = sstock.stockEtatQty();
var message = string.Join(Environment.NewLine,
q.Select(item => String.Format("{0} {1} {2}", item.EAN, item.Title, item.Quantity)));
MessageBox.Show(message);
}

Since the static type information about the object of anonymous type is lost by the time that you exit stockEtatQty() method, you could cast the object to dynamic and access fields like this:
str = string.Join(Environment.NewLine, q.Cast<dynamic>().Select(item =>
string.Format("{0} {1} {2}", item.EAN, item.Title, item.Quantity)
));
The cast to dynamic tells the compiler that EAN, Title, and Quantity need to be resolved at runtime.
Note that I also replaced the foreach loop with a call to string.Join to improve performance: repeated string concatenation creates unnecessary partial string objects, which string.Join avoids. Another solution would be to use StringBuider instead of string concatenation +=.
stockEtatQty is in a project (Service) and QuantityToolStripMenuItem_Click is in another project (View)
Unfortunately, this means that you would not be able to use anonymous types: anonymous types are generated with internal visibility, limiting their use to the assembly in which they are produced. You can use a work-around based on ExpandoObject described in this answer:
var q = afList.Select(x => {
dynamic res = new ExpandoObject();
res.EAN=x.EAN;
res.Title=x.title;
res.Quantity=x.qty;
return res;
});

Create a new class that represents the new object structure and return that.
var q = from x in afList
select new SmallerType { EAN=x.EAN, Title=x.title, Quantity=x.qty };
WinForm Function
foreach (SmallerType item in q)
{
//
}

You can use collection of dynamic objects instead of simple objects as return type of your method:
public IEnumerable<dynamic> stockEtatQty()
Then you will not have IntelliSense but at runtime properties will be found:
foreach (var item in sstock.stockEtatQty())
str += String.Format("{0}", item.EAN) + Environment.NewLine;
But I suggest you to create custom class with EAN, Title and Quantity properties. Or just use your allFields instead of anonymous objects.
Consider also to use StringBuilder for string creation to avoid creating lot of in-memory strings:
var builder = new StringBuilder();
foreach (var item in sstock.stockEtatQty())
builder.AppendFormat("{0}{1}", item.EAN, Environment.NewLine);
MessageBox.Show(builder.ToString());

Related

How to properly access object's List<> value in C#?

I am trying to get the object value but I don't know how to do it. I'm new to C# and its giving me syntax error. I want to print it separately via the method "PrintSample" How can I just concatenate or append the whatData variable . Thank you.
PrintSample(getData, "name");
PrintSample(getData, "phone");
PrintSample(getData, "address");
//Reading the CSV file and put it in the object
string[] lines = File.ReadAllLines("sampleData.csv");
var list = new List<Sample>();
foreach (var line in lines)
{
var values = line.Split(',');
var sampleData = new Sample()
{
name = values[0],
phone = values[1],
address = values[2]
};
list.Add(sampleData);
}
public class Sample
{
public string name { get; set; }
public string phone { get; set; }
public string adress { get; set; }
}
//Method to call to print the Data
private static void PrintSample(Sample getData, string whatData)
{
//THis is where I'm having error, how can I just append the whatData to the x.?
Console.WriteLine( $"{getData. + whatData}");
}
In C# it's not possible to dynamically evaluate expression like
$"{getData. + whatData}"
As opposed to languages like JavaScript.
I'd suggest to use rather switch expression or Dictionary<string, string>
public void PrintData(Sample sample, string whatData)
{
var data = whatData switch
{
"name" => sample.name,
"phone" => sample.phone,
"address" => sample.address
_ => throw new ArgumentOutOfRangeException(nameof(whatData)),
};
Console.WriteLine(data);
}
I'm not sure what you are trying to achieve. Perhaps this will help you:
private static void PrintSample(Sample getData, string whatData)
{
var property = getData.GetType().GetProperty(whatData);
string value = (string)property?.GetValue(getData) ?? "";
Console.WriteLine($"{value}");
}
What PO really needs is
private static void PrintSamples(List<Sample> samples)
{
foreach (var sample in samples)
Console.WriteLine($"name : {sample.name} phone: {sample.phone} address: {sample.address} ");
}
and code
var list = new List<Sample>();
foreach (var line in lines)
{
......
}
PrintSamples(list);
it is radicolous to use
PrintSample(getData, "name");
instead of just
PrintSample(getData.name)
You can do this using reflection. However, it's known to be relatively slow.
public static void PrintSample(object getData, string whatData)
{
Console.WriteLine( $"{getData.GetType().GetProperty(whatData).GetValue(getData, null)}");
}

How to get one result from a list in another method?

Hi all I'm new to C#.
I try to return a result "totalAmount" from my method called "GetAllKschl". In this method I returned a list with "KSCHL, KSCHLData, price, pieces und totalPrice".
So in my new method I need the total amount of all "totalPrice" together.
first method:
public List<Result> GetAllKschl(string fileNameResult, string fileNameData)
{
List<Result> listResult = new List<Result>();
docResult.Load(fileNameResult);
docData.Load(fileNameData);
var resultList = docResult.SelectNodes("//root/CalculationLogCompact/CalculationLogRowCompact");
foreach (XmlNode nextText in resultList)
{
XmlNode KSCHL = nextText.SelectSingleNode("KSCHL");
string nextKschl = KSCHL.InnerText;
// ... and so on...
if (pieces > 0 && totalPrice > 0)
{
listResult.Add(new Result(nextKschl, nextKSCHLData, nextEinzelpreis, pieces, totalPrice));
}
}
return listResult;
}
second method: (don't know exactly what to do)
public decimal GetTotalAmount(string amount, string totalAmount)
{
string total = GetAllKschl(amount, totalAmount); // ??
return total;
}
So here I want to have just the TotalAmount (every totalPrice from GetAllKschl) und not the whole list from GetAllKschl. How do I do this?
here my class result:
public class Result
{
public string KSCHL { get; set; }
public string Info { get; set; }
public int individualPrice { get; set; }
public int Pieces { get; set; }
public int TotalCosts { get; set; }
public Result(string kschl, string info, int individualPrice, int pieces, int totalCosts)
{
KSCHL = kschl;
Info = info;
IndividualPrice = individualPrice;
Pieces = pieces;
TotalCosts = totalCosts;
}
}
You can use LINQ extension method Sum to do so:
decimal total = GetAllKschl( amount, totalAmount ).Sum( result => result.Gesamtpreis );
I assume that the TotalPrice is the name of the property for price in the Result class.
The Sum extension method iterates over all items in the returned collection and sums up the prices.
You could rewrite this without LINQ like this:
var list = GetAllKschl( amount, totalAmount );
decimal total = 0;
foreach ( var item in list )
{
total += item.Gesamtpreis;
}
As a suggestion, I would recommend making clearer variable naming conventions and do not mix variable names from different languages (English and German).
Also it is quite unusual you used decimal for the total price while Result class uses int. Maybe result should have decimals as well? It seems fitting for a price property.
The best would be probably using LINQ:
public decimal GetTotalAmount(string amount, string totalAmount)
{
var total = GetAllKschl(amount, totalAmount).Sum(result => result.Gesamtpreis);
return total;
}
I'm assuming that Your result are in the property called Gesamtpreis and is of any numeric type.
EDIT:
Based on the comments I decided to put there a bit more description about LINQ extension methods and lambda method. LINQ methods allows You to use a query language similar to SQL. It works with Collection of elements (e.g. List of Result - List<Result>). On this collection You will call these methods and they will provide You some kind of result, sometimes just number (Aggregate functions like Min,Max,Sum,..) or they will do some other actions returning object or another collection (First,Last, ToList, ToDictionary).
In our care we will have a List with objects:
public class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
List<Product> productList = new List<Product>();
productList.Add(new Product() { Name = "Car", Price = 140000 });
productList.Add(new Product() { Name = "SSD Disc", Price = 2000 });
productList.Add(new Product() { Name = "Bananan", Price = 7 });
Having those, for normal SUM You would go with:
int result = 0;
foreach(var nProduct in productList)
result += nProduct.Price;
Console.WriteLine(result);
This is kind of short code, but it can be pretty much simplified without using variable (for intermediate results) and foreach cycle. (Actually the foreach cycle will be used but we won't need to handle/write it.) LINQ example:
var result = productList.Sum(nProduct => nProduct.Price);
Now this code is much shorter, but we have to split it into several parts to understand what actually happened:
// Saving result to variable (as anytime before)
// Note that I have changed "int" to "var", which simplifies the code,
// as You don't have to take care of "what type will the result be"
// usage is really common with LINQ also
var result =
// calling method Sum() on the productList
productList.Sum()
// Sum will take each object in the collection and put it as a parameter called "nProduct"
// now the "=>" is something called Lambda syntax,
// that allows take paremeters from the left side and use them in code on the right side.
// left side is instance of "Product" class named as "nProduct"
Sum(nProduct => ... )
// ... is replaced with "nProduct.Price",
// which is selector that tells "make the sum of property "Price"
Sum(nProduct => nProduct.Price)
// In similar manner works other aggregate functions
var max = productList.Max(prod => prod.Price);
var min = productList.Min(prod => prod.Price);
var avg = productList.Average(prod => prod.Price);
//In this method you are returning a List
public List<Result> GetAllKschl(string fileNameResult, string fileNameData)
{
List<Result> listResult = new List<Result>();
docResult.Load(fileNameResult);
docData.Load(fileNameData);
var resultList = docResult.SelectNodes("//root/CalculationLogCompact/CalculationLogRowCompact");
foreach (XmlNode nextText in resultList)
{
XmlNode KSCHL = nextText.SelectSingleNode("KSCHL");
string nextKschl = KSCHL.InnerText;
// ... and so on...
if (pieces > 0 && totalPrice > 0)
{
listResult.Add(new Result(nextKschl, nextKSCHLData, nextEinzelpreis, pieces, totalPrice));
}
}
return listResult;
}
//On the second method you returning a decimal and expecting a string
public decimal GetTotalAmount(string amount, string totalAmount)
{
string total = GetAllKschl(amount, totalAmount); // ??
return total;
}
It would be best to change the second method as:
decimal total = GetAllKschl(amount, totalAmount).Sum(result => result.Gesamtpreis);
Add linq in the return.

How do I replace words in a string that matches a keywords stored in an array with data from a table?

I want to replace words in a string that matches a keywords stored in an array with data from matching column in a table.
My model is People
public class People()
{
public int Id { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
The method i have tried is :
public void ProcessString(string message)
{
using (DBEntities db = new DBEntities())
{
var people = db.People.ToList();
foreach(var person in people)
{
string[] keyword = {"#Title", "#Name", "#Surname"};
for (int i=0; i<keyword.Length; i++)
{
string updatedString = Regex.Replace(body, keyword[i], matchingcolumndata);
}
}
}
So instead of matchingcolumndata in the string updatedString = Regex.Replace(body, keyword[i], matchingcolumndata); line i want to put the data that is matching the column in my table People. Please help with the question if its not straight to the point.
From my understand it may be you are looking for
foreach(var person in people)
{
string[] keyword = {"#Title", "#Name", "#Surname"};
body= body.Replace(keyword[0],person.Title);
body= body.Replace(keyword[1],person.Name);
body= body.Replace(keyword[2],person.Surname);
}
It doesn't make any sense to declare your keywords array inside the loop. If you want to do this in a neat way (and subject to modifications easily), you can create a map between the keyword and the property of the Person. Like this:
using (DBEntities db = new DBEntities())
{
var people = db.People.ToList();
// maps each keyword to a property in the Person class
var keywordPropertyMapping = new Dictionary<string, Func<Person, string>>()
{
{ "#Title", p => p.Title },
{ "#Name", p => p.Name },
{ "#Surname", p => p.Surname }
};
foreach (var person in people)
{
foreach(var keywordFunc in keywordPropertyMapping)
{
body = body.Replace(keywordFunc.Key, keywordFunc.Value(people));
}
}
}
For any new keyword, you just add one simple line inside the dictioanry keywordPropertyMapping, and the magic works.
If you're not familiar with Func<T, TResult> which is one critical part of the Linq magic, then read the Docs:
Encapsulates a method that has one parameter and returns a value of
the type specified by the TResult parameter.
You can use this delegate to represent a method that can be passed as
a parameter without explicitly declaring a custom delegate. The
encapsulated method must correspond to the method signature that is
defined by this delegate. This means that the encapsulated method must
have one parameter that is passed to it by value, and that it must
return a value.

Issue displaying LINQ query results in a grid in C#

I received some help here with the following LINQ query, but am still struggling with it. The result I'm trying to obtain is to display some attributes and their values from an xml file in a DataGridView control. I'm calling my method from a button click and am trying to pass back the list for display in the grid. Here is an example of the row:
<z:row CenterCode="JAX" CenterName="Jacksonville" Version="1.0" NextExport="66742" NextImport="29756" LastImportTime="2015-06-10T14:48:33" FtpProxyServer="" FtpUserName="" FtpPassword="" ResetImportID="False"/>
Here is the method:
public static List<string[]> MonitorCounts(string upperLimit)
{
// Load xml
XDocument xmldoc = XDocument.Load(#"c:\XML\Configuration.xml");
XNamespace z = "#RowsetSchema";
Int32 limit = Convert.ToInt32(upperLimit);
var elementQuery = xmldoc.Descendants(z + "row").Where(e => (long?)e.Attribute("NextExport") > limit | (long?)e.Attribute("NextImport") > limit);
var attributes = elementQuery.Select(e => e.Attributes().Select(a => new KeyValuePair<string, string>(a.Name.LocalName, (string)a)).ToList()).ToList();
return attributes;
}
My questions are how to select only specific attributes and values in attributes. If I do something like this:
var attributes = elementQuery.Select(e => e.Attributes("CenterName").Select(a => new KeyValuePair<string, string>(a.Name.LocalName, (string)a)).ToList()).ToList();
then this is returned:
[0] = {[CenterName, Jacksonville]}
I need to select this and 4 others. I'm also getting a convrsion error - Cannot implicitly convert type 'System.Collections.Generic.List<System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string,string>>>' to 'System.Collections.Generic.List<string[]>. Appreciate any pointers to help me along.
You can use an anonymous type:
var attributes =
elementQuery.Select(e => new
{
CenterName = (string)e.Attribute["CenterName"],
Version = (string)e.Attribute["Version"],
// more attributes
}).ToList();
You can't however return this from the method in a useful way. So if you really need both the attribute name and the attribute value as strings, try this approach instead:
var attributes =
elementQuery.Select(e => new []
{
Tuple.Create("CenterName", (string)e.Attribute["CenterName"]),
Tuple.Create("Version", (string)e.Attribute["Version"]),
// more attributes
}).SelectMany(x => x).ToList();
The return type of your method now has to be List<Tuple<string, string>>.
And finally, if you actually need a List<string[]> as the return type, use this code:
var attributes =
elementQuery.Select(e => new []
{
new [] { "CenterName", (string)e.Attribute["CenterName"] },
new [] { "Version", (string)e.Attribute["Version"] },
// more attributes
}).SelectMany(x => x).ToList();
I solved my own problem. Here is what I did:
Created a class for the attributes needed:
public class dataRow
{
public string CenterName { get; set; }
public string CenterCode { get; set; }
public string NextImport { get; set; }
public string NextExport { get; set; }
public string LastImportTime { get; set; }
}
Selected the results into it:
List<dataRow> dataRows = elementQuery.Select( e => new dataRow
{ CenterName = (string)e.Attribute("CenterName"),
CenterCode = (string)e.Attribute("CenterCode"),
NextImport = (string)e.Attribute("NextImport"),
NextExport = (string)e.Attribute("NextExport"),
LastImportTime = (string)e.Attribute("LastImportTime") }).ToList();
Changed my method to return the correct object:
public static List<dataRow> MonitorCounts(string upperLimit)
Set my grids datasource to the method return:
dataGridView1.DataSource = xmlProcessing.MonitorCounts(tbxUpperLimit.Text.ToString());
return dataRows;

Reflection on IQueryable<T> can't find property

I created a custom gridview control and exported it into a dll so I can reuse it. Inside the dll I created a function to get the DataSource, I'm trying to fill a dropdown from there but is failing.
So on my website I have this
public partial class _Management : System.Web.UI.Page
{
public class _ManagementHelper
{
public int ID;
public string CompanyName;
public string ResourceName;
}
protected void Page_Load(object sender, EventArgs e)
{
ucGridViewEx.DataSource = ucGridViewEx_Source();
ucGridViewEx.DataBind();
}
private List<dynamic> ucGridViewEx_Source()
{
var source = dl.ComapniesResources.Select(x => new _ManagementHelper
{
ID = x.ResourceID,
CompanyName = x.Supplier1.SupplierName,
ResourceName = x.Name
});
return ucGridViewEx.GridViewExDataSource(source);
}
Then the custom control inside the dll have this relevant code
public List<dynamic> GridViewExDataSource<T>(IQueryable<T> query)
{
foreach (var column in this.Columns)
{
var gridViewExColumn = column as ColumnEx;
if (gridViewExColumn != null
&& gridViewExColumn.SearchType == SearchTypeEnum.DropDownList)
{
gridViewExColumn.DropDownDataSource = query.GetDropDownDataSource(gridViewExColumn.DataField);
}
}
return ((IQueryable<dynamic>)query).ToList<dynamic>();
}
Function GetDropDownDataSource() is inside another extension class inside the same dll as the gridview
internal static List<ListItem> GetDropDownDataSource<T>(this IQueryable<T> query,
string dataField)
{
var ddlSource = new List<ListItem>();
// x =>
var xParameter = Expression.Parameter(typeof(T), "x");
// x.Property
var propery = typeof(T).GetProperty(dataField);
// x => x.Property
var columnLambda = Expression.Lambda(Expression.Property(xParameter, propery), xParameter);
return ddlSource;
}
Code fails in this where I'm assingning the value to columnLambda because property is null, not because it does not exist (it does) because is not getting any property. I tried with GetProperties() and is not returning anything.
Is curious than this is happening since I moved to the DataSource to select into _ManagementHelper. I was using a dynamic ( Select(x => new {}) ) on ucGridViewEx_Source() before and it worked perfectly. Please don't provide the solution to keep using the dynamic because I need to allow both types, with dynamic and using custom objects.
_ManagementHelper has no property. It just contains three fields (as far as you told us). So GetPrperty returns nothing. Change the members of _ManagementHelper to properties:
public class _ManagementHelper
{
public int ID { get; set; }
public string CompanyName { get; set; }
public string ResourceName { get; set; }
}
I see one bug --
var source = dl.ComapniesResources.Select(x => new _ManagementHelper
{
// ResourceID = x.ResourceID, this was the old code
ID = x.ResourceID, // fixed code
CompanyName = x.Supplier1.SupplierName,
ResourceName = x.Name
});
also, where is ListItem defined?

Categories

Resources