This question already has answers here:
C#: how to return a list of the names of all properties of an object?
(4 answers)
Closed 6 years ago.
I'd like to be able to get the values and count from any model I create.
For example let's say I have a model that looks like this.
public class test
{
public int ID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
I want to be able to write code that will look at the model and then get ID, Name, Address and put them into an array. And I don't want the values. But the values from the model. Not the data. As well as getting the count of the values. 3.
EDIT: per your clarifications in the comments
You can use reflection to extract the property names into a list
var foo = new test();
IList<string> properties = foo.GetType().GetProperties()
.Select(p => p.Name).ToList();
OLD ANSWER
Try converting your object to a NameValueCollection (https://msdn.microsoft.com/en-us/library/system.collections.specialized.namevaluecollection(v=vs.110).aspx). This collection offers a count, and allows you hash table access to the values. You can also retrieve an IEnumerable for the Values (or Keys) to suit your needs.
var foo = new test();
NameValueCollection formFields = new NameValueCollection();
foo.GetType().GetProperties()
.ToList()
.ForEach(pi => formFields.Add(pi.Name, pi.GetValue(foo, null).ToString()));
NOTE: if .ToString() is too destructive, you can swap the NameValueCollection with the IDictionary implementation of your choice.
Code modified from this question: how to convert an instance of an anonymous type to a NameValueCollection
In the other class simply call test.ID = identification;
identification being the variable that you would like ID to get the value from.
test being your class name.
I believe you are looking for something like this (wrapped in example code).
class Program
{
public class Test
{
public int ID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
static void Main(string[] args)
{
var propertyInfo = typeof(Test).GetProperties();
var propertyCount = propertyInfo.Count();
Console.WriteLine($"Property count is {propertyCount}");
foreach (var info in propertyInfo)
{
Console.WriteLine($"Property Name: {info.Name}");
}
Console.ReadKey();
}
}
Which would give you the output:
Code Output
This allows you to get the property count and property names of a class.
Related
I'm fetching data from website that returns me an object in a string like this:
{
index: 1,
commentNumber: 20,
feedComments: {
3465665: {
text: "I do not agree",
likeRatio: 0
},
6169801: {
text: "Hello",
likeRatio: 12
},
7206201: {
text: "Great job!",
likeRatio: 5
}
}
}
I want to work with this as an object, that's pretty easy to do, I'll just do this:
string objectString = GetData(); // Artificial GetData() method
dynamic data = JObject.Parse(objectString);
And now I can easily get all properties I want from this object using dynamic
The problem is pretty obvious now, I want to get properties, whose name starts with number (the object data structure I fetch is just designed that way). But property/field names you get from object cannot begin with a number.
int commentNumber = data.commentNumber; // Works fine
string commentText = data.feedComments.3465665.text; // Obviously won't compile
Is there any way to do this?
Note that I want to work with data I fetch as it was an object, I know I get get the comment text right from the string that GetData() method returns using some regex or something, but that's something I want to avoid.
You should really be parsing the JSON into concrete C# classes. Dynamic is slow and vulnerable to runtime errors that are hard to detect.
The comments will go into a Dictionary. For example:
public class Root
{
public int Index { get; set; }
public int CommentNumber { get; set; }
public Dictionary<long, FeedComment> FeedComments { get; set; }
}
public class FeedComment
{
public string Text { get; set; }
public int LikeRatio { get; set; }
}
And deserialise like this:
var result = JsonConvert.DeserializeObject<Root>(objectString);
Now you can access the comments very easily:
var commentText = result.FeedComments[3465665].Text
I have re edit this question below I have an example file which as multiple purchase orders in the file which is identified by the second column.
Order Number, Purchase Number,DATE,Item Code ,Qty, Description
1245456,98978,12/01/2019, 1545-878, 1,"Test"
1245456,98978,12/01/2019,1545-342,2,"Test"
1245456,98978,12/01/2019,1545-878,2,"Test"
1245456,98979,12/02/2019,1545-878,3,"Test 3"
1245456,98979,12/02/2019,1545-342,4,"Test 4"
1245456,98979,12/02/2019,1545-878,5,"Test 4"
What I want the end result to be is to be able to place the above into one class like the following
At the min I am using filelpers to parse the csv file this would work fine if I had sep header file and row file but they are combined as you see
var engine = new FileHelperEngine<CSVLines>();
var lines = engine.ReadFile(csvFileName);
So the Class should be like below
[DelimitedRecord(",")]
public class SalesOrderHeader
{
private Guid? _guid;
public Guid RowID
{
get
{
return _guid ?? (_guid = Guid.NewGuid()).GetValueOrDefault();
}
}
public string DocReference { get; set; }
public string CardCode { get; set; }
public string DocDate { get; set; }
public string ItemCode { get; set; }
public string Description { get; set; }
public string Qty { get; set; }
public string Price { get; set; }
[FieldHidden]
public List<SalesOrderHeader> OrdersLines { get; set; }
}
What I imagine I will have to do is two loops as you will see from my createsales order routine i first create the header and then add the lines in.
public void CreateSalesOrder(List<SalesOrderHeader> _salesOrders)
{
foreach (var record in _salesOrders.GroupBy(g => g.DocReference))
{
// Init the Order object
oOrder = (SAPbobsCOM.Documents)company.GetBusinessObject(SAPbobsCOM.BoObjectTypes.oOrders);
SAPbobsCOM.SBObob oBob;
// set properties of the Order object
// oOrder.NumAtCard = record.Where(w=>w.RowID = record.Where()
oOrder.CardCode = record.First().CardCode;
oOrder.DocDueDate = DateTime.Now;
oOrder.DocDate =Convert.ToDateTime(record.First().DocDate);
foreach (var recordItems in _salesOrders.SelectMany(e=>e.OrdersLines).Where(w=>w.DocReference ==record.First().DocReference))
{
oOrder.Lines.ItemCode = recordItems.ItemCode;
oOrder.Lines.ItemDescription = recordItems.Description;
oOrder.Lines.Quantity = Convert.ToDouble(recordItems.Qty);
oOrder.Lines.Price = Convert.ToDouble(recordItems.Price);
oOrder.Lines.Add();
log.Debug(string.Format("Order Line added to sap Item Code={0}, Description={1},Qty={2}", recordItems.ItemCode, recordItems.Description, recordItems.Qty));
}
int lRetCode = oOrder.Add(); // Try to add the orer to the database
}
if(lRetCode == 0)
{
string body = "Purchase Order Imported into SAP";
}
if (lRetCode != 0)
{
int temp_int = lErrCode;
string temp_string = sErrMsg;
company.GetLastError(out temp_int, out temp_string);
if (lErrCode != -4006) // Incase adding an order failed
{
log.Error(string.Format("Error adding an order into sap ErrorCode {0},{1}", temp_int, temp_string));
}
}
}
The problem you will see i have is how do I first split the csv into the two lists and second how do i access the header rows correctly in the strongly type object as you see I am using first which will not work correctly.
With FileHelpers it is important to avoid using the mapping class for anything other than describing the underlying file structure. Here I suspect you are trying to map directly to a class which is too complex.
A FileHelpers class is just a way of defining the specification of a flat file using C# syntax.
As such, the FileHelpers classes are an unusual type of C# class and you should not try to use accepted OOP principles. FileHelpers should not have properties or methods beyond the ones used by the FileHelpers library.
Think of the FileHelpers class as the 'specification' of your CSV format only. That should be its only role. (This is good practice from a maintenance perspective anyway - if the underlying CSV structure were to change, it is easier to adapt your code).
Then if you need the records in a more 'normal' object, then map the results to something better, that is, a class that encapsulates all the functionality of the Order object rather than the CSVOrder.
So, one way of handling this type of file is to parse the file twice. In the first pass you extract the header records. Something like this:
var engine1 = new FileHelperEngine<CSVHeaders>();
var headers = engine1.ReadFile(csvFileName);
In the second pass you extract the details;
var engine2 = new FileHelperEngine<CSVDetails>();
var details = engine2.ReadFile(csvFileName);
Then you combine this information into a new dedicated class, maybe with some LINQ similar to this
var niceOrders =
headers
.DistinctBy(h => h.OrderNumber)
.SelectMany(d => details.Where(d => d.OrderNumber = y))
.Select(x =>
new NiceOrder() {
OrderNumber = x.OrderNumber,
Customer = x.Customer,
ItemCode = x.ItemCode
// etc.
});
Hi I'm having difficulty finding an answer to my question here, so I figured I'd just ask. I have to lists of classes, ServiceItem, and ServiceDetailsClass. I want to filter out all of the ServiceDetailClass Items that are not int ServiceItems list. Here are the two classes:
public class ServiceItem
{
public long ID { get; set; }
public string Status { get; set; }
}
public class ServiceDetailsClass
{
public string Name;
public long ID;
public int Quantity;
public string Notes;
public string Status;
public string Description;
public DateTime CreatedDate;
}
So far the only things I've found on here is for lists that have a list in them, so this is a bit different. This is all I was able to come up with, but the filter list has 0 item, even though I know it should have more than that:
lstFilteredServiceDetailsClass = lstServiceDetailsClass.Where(i => lstServiceItem.Contains
(new ServiceItem { lngId = i.ServiceID, strStatus = "Locked" })
Any help is appreciated. Thanks!
You're making a new object and then checking the list to see if that exact object/instance is in it (i.e. because it's an object, it's comparing the reference).
Instead, you need to look for overlapping IDs.
Something like this should work:
List<ServiceItem> serviceItems;
List<ServiceItemDetails> serviceItemDetails;
var result = serviceItemDetails.Where(sid => serviceItems.Any(si => si.ID == sid.ID))
In English: "The collection of ServiceItemDetails where the list of service items has an item with the same ID"
I am using generic method to fill my dropdown for all types
below is my code.
the entity type are as follow
public class Role
{
public string Id { get; set; }
public string Name { get; set; }
}
public class DropDown
{
public string Id { get; set; }
public string Name { get; set; }
}
i am able to fetch data successfully at
var data = DataFetcher.FetchData<T>();
private static void Main( string[] args )
{
List<DropDown> cities = BLL.GetDataList<City>();
List<DropDown> states = BLL.GetDataList<State>();
List<DropDown> roles = BLL.GetDataList<Role>();
}
public static class BLL
{
public static List<DropDown> GetDataList<T>() where T : class ,new()
{
var data = DataFetcher.FetchData<T>();
return data as List<DropDown>;
}
}
I knew this cast data as List<DropDown> will fail,thats why its returning null back to calling method,
How can i cast Generic list to List of Known Type?
You have to ask yourself: how do I want to convert T to DropDown? If you can't answer this, the answer is: you can't.
I guess your DropDown class has an object Value property, that holds the dropdown value, and you wish to assign the data entity to that property.
Then you can project the list of data entities to DropDowns as such:
var data = DataFetcher.FetchData<T>();
return data.Select(d => new DropDown { Value = d }).ToList();
As for your edit: so you have at least one type, the displayed Role, that has an Id and Name property. But type T doesn't guarantee this, so you'd need to introduce an interface:
public interface INamedIdentifyableEntity
{
string Id { get; set; }
string Name { get; set; }
}
And apply this to your entities. Then introduce it as a generic constraint and do the mapping:
return data.Select(d => new DropDown
{
Id = d.Id,
Name = d.Name,
}).ToList();
But you don't want this, as here you are tying these two properties to dropdowns. Tomorrow you'll want an entity with Code instead of Id and Text instead of Name, so you'll have to add more interfaces, more overloads, and so on.
Instead you might want to use reflection, where you can specify the member names in the call:
List<DropDown> cities = BLL.GetDataList<City>(valueMember: c => c.CityCode, displayMember: c => c.FullCityname);
And use these member expressions to look up data's values and fill those into the DropDown.
However, you're then reinventing the wheel. Leave out your DropDown class entirely, and leave the dropdown generation to the front end, in this case MVC:
var cities = DataFetcher.FetchData<City>();
var selectList = new SelectList(cities.Select(c => new SelectListItem
{
Selected = (c.Id == selectedCityId),
Text = c.FullCityName,
Value = c.CityCode,
});
Or:
var selectList = new SelectList(cities, "CityCode" , "FullCityName", selectedCityId);
One solution is to use AutoMapper.
First create a map between your models like this:
AutoMapper.Mapper.CreateMap<Role, DropDown>();
Do the same thing for City and State classes if you need to.
Then you can use AutpMapper to convert your objects to DropDown like this:
public static List<DropDown> GetDataList<T>() where T : class ,new()
{
var data = DataFetcher.FetchData<T>();
return data.Select(x => AutoMapper.Mapper.Map<DropDown>(x)).ToList();
}
If I understood the question correctly, you could use Linq as follows.
return data.Cast<DropDown>().ToList();
I have a class definition that looks like this:
public class MyObjectModel
{
public int ObjectID { get; set; }
//for when the user's data is split in 2 fields
public string FirstName { get; set; }
public string LastName { get; set; }
//for when the user's data is all in one field
public string FirstLastName { get; set; }
}
I have a list of these MyObjectModel and I want to sort them by name with a custom sort process because that involves checking if the data contains a LastName (in this case sort using the LastName) or just FirstLastName (in this case I'm going to break the string and sort on the second term, if there's one, or just the whole string if there's only one word.)
I'm not sure on two things:
should I use IComparer or IComparable?
Once it determines the order of the sort (I can do that), how do I make it so that the output of the method is a list of ints representing ObjectID.
Use Linq:
List<MyObjectModel> objects = new List<MyObjectModel>();
List<int> ids = objects.OrderBy(o => FunctionWhichReturnsNameForSort(o)).Select(o => o.ObjectID).ToList();
FunctionWhichReturnsNameForSort can be implemented in another class, or an extension, or as a member.
// should be in a static helper class
public static string NameForSort(this MyObjectModel obj)
{
if (!string.IsNullOrEmpty(obj.LastName)) return obj.LastName;
return obj.FirstLastName.Split(... // your splitting logic goes here
}
var ids = objects.OrderBy(o => o.NameForSort()).Select(o => o.ObjectID).ToList();
When you really need this strange double solution then you will run into this and similar problems more often. As a more general solution, consider putting the business logic for names in a few read-only properties:
//for when the user's data is split in 2 fields
public string FirstName { get; set; }
public string LastName { get; set; }
//for when the user's data is all in one field
public string FirstLastName { get; set; }
public string FullName
{
get { ... } // pick from LastName, FirstName, FirstLastName
}
public string SortName
{
get { ... } // pick from LastName, FirstLastName
}
Once it determines the order of the sort (I can do that), how do I make it so that the output of the method is a list of ints representing ObjectID
result = MyObjectList
.OrderBy(m => m.SortName) // sort on SortName
.Select(m => m.ObjectID) // select the Id
.ToList();
If this sorting is specific to one use case, it can be achieved using LINQ:
var sortedIds = models.OrderBy(SecondName).Select(m => m.ObjectId).ToList();
private static string SecondName(MyObjectModel model)
{
if (!string.IsNullOrWhitespace(model.LastName)) return model.LastName;
return model.FirstLastName.Split(' ').Last();
}
While you can use LINQ, as others have suggested, that would involve creating a brand new list, not mutating the existing list. That may or may not be preferable. If you want to sort the list itself that's easy enough too:
List<string> list = new List<string>(){"a","b","c"};
list.Sort((a,b)=> a.CompareTo(b));
Just take your list, call Sort, and pass in a lambda that takes two items and returns an integer indicating which is greater. In your case, just call some method on a and b to get a string and then use CompareTo or string.Compare on those two strings.