I am facing one issue with linq query in c# , my linq query as per below
list = (from row in dt.AsEnumerable()
select new Perfmon
{
id = row.Field<long>("id"),
counter1 = row.Field<string>("counter"),
}).ToList();
I have one perfmon class and it contains properties like (id, counter1, counter2 ...)
there are more then 20 counters now i have developed sql query to select id and counter based on passed countername in parameter e.g. if i have passed counter1 then it will select id , counter1 (renamed as counter) only
if i will use switch case here then it will have 20 witch case, can anyone please help me how can bind property dynamically in linq?
Thanks in advance.
You can make your Perfmon class backed by a dictionary rather than fields per properties. like:
class Perfmon
{
private readonly Dictionary<string, string> _counters = new Dictionary<string, string>();
public Perfmon(params KeyValuePair<string, string>[] knownCounters)
{
foreach (var knownCounter in knownCounters)
{
SetCounter(knownCounter.Key, knownCounter.Value);
}
}
public void SetCounter(string name, string value)
{
_counters[name] = value;
}
protected string GetCounterValue(string name)
{
if (_counters.ContainsKey(name))
return _counters[name];
else
return null;
}
public string Counter1 { get { return GetCounterValue("Counter1"); } }
public string Counter2 { get { return GetCounterValue("Counter2"); } }
public string Counter3 { get { return GetCounterValue("Counter3"); } }
}
The constructor is there so you can easily use it in your query like:
var counterName = "Counter2";
list = (from row in dt.AsEnumerable()
select new Perfmon(new KeyValuePair<string, string>(counterName, row.Field<string>("counter")))
{
id = row.Field<long>("id")
}).ToList();
You can also use Dynamic Linq. The essence is to parse your string and convert it in to expression trees. See here for more details
http://weblogs.asp.net/rajbk/archive/2007/09/18/dynamic-string-based-queries-in-linq.aspx
I think you should look into the System.Reflection namespace. Sure, you want to use it within a linq query, but the thing you can't seem to do, for now, is to dynamically extract a field/property from a class.
Try to use it outside a linq query, and then you'll find a way to integrate it into your query.
Related
I am trying to populate a list of a custom class using the AddRange method.
Here is the custom class:
public class Hook
{
public Hook(System.Data.DataRow values)
{
this.text = values[0].ToString();
this.category = values[1].ToString();
this.weather = values[2].ToString();
this.timeofday = values[3].ToString();
}
public string text { get; set; }
public string category { get; set; }
public string[] associatedLandmarks { get; set; }
public string weather { get; set; }
public string timeofday { get; set; }
}
Here is my object array that I need to filter objects from, I am populating it using values from a datatable:
int numberofhooks = result.Tables[1].Rows.Count - 1;
Hook[] Hooks = new Hook[numberofhooks];
for (int i = 0; i < numberofhooks; i++)
{
Hooks[i] = new Hook(result.Tables[1].Rows[i]);
}
I need to filter out a subset of these objects using a variable that gets updated on the fly, which is a property of the class.
'category' is the property
I am using the AddRange method to do this, but it isn't working. What can be the issue with this? Or is there another way to accomplish this?
List<Hook> RelevantHooks = new List<Hook>();
foreach (string category in relevantHookCategories)
{
RelevantHooks.AddRange(from item in Hooks
where item.category == category
select item);
}
Thanks
I see two problems here:
If you want all the rows from your table why are you using:
int numberofhooks = result.Tables[1].Rows.Count - 1;
It should be:
int numberOfHooks = result.Tables[1].Rows.Count;
Perhaps the last item that you are excluding has the category you are looking for.
When you say "on the fly" I'm assuming that you want to have the subset RelevantHooks updated even after your query, when modifying Hooks, if that's the case then you don't have to use a List, just define your relevantHooks as:
IEnumerable<Hook> relevantHooks = Hooks.Where(h => relevantHookCategories.Contains(h.category));
e.g. If you modify the category property for any of Hooks items:
Hooks[0].category="Other Category Value";
Will either exclude or include the first hook from relevantHooks
RelevantHooks.AddRange(Hooks.Where(hook => category == hook.category));
should be enough. You don't necessarily need the select clause in this case.
No need to use AddRange. Just iterate Hooks once and check if it's one of the categories.
RelevantHooks = Hooks.Where(h => relevantHookCategories.Contains(h.category)).ToList();
Try this:
RelevantHooks.AddRange((from item in Hooks
where item.category == category
select item).ToArray());
I don't think it should matter, but it could be the AddRange method is failing because it's being passed IQueryable when it is expecting IEnumerable
Given my last Comment, here's what I meant:
for (int i = 0; i < numberofhooks; i++)
{
Hooks[i] = new Hook(result.Tables[1].Rows[i]);
if(relevantHookCategories.Contains(Hooks[i].category)
RelevantHooks.Add(Hook[i]);
}
I've already searched through StackOverflow (and other websites) about transforming a DataTable to List with reflection in C#.
My results until now are pretty good: I can reflect 200k lines in 3.5 seconds (0.5 seconds in hardcoded mode).
But my entities (the classes that represent my data, but I think you already know that) follow this pattern:
My database have columns like this (I don't actually do this, but you'll get the idea):
Table: Clients
Columns:
ClientID, ClientName, ClientPhone, CityID[FK]
I'm using SqlConnection (MySqlConnection), so I have to hardcode my entities and transform the database result in a list of this entity. Like:
Select *, cit.* from Clients cli
Inner join Cities cit on (cit.CityID == cli.CityID)
Inner join Countries cou on (cou.CountryID == cit.CountID)
I don't know if this SQL is correct, but I think you got the idea. This should return some fields like this:
ClientID, ClientName, ClientPhone, CityID, CityName, CountryID, CountryName
Shoud result a List<Client>.
Here's the problem: I have 2 inner joins and I represent this data in my entities like this (I like the expression "like this"):
public class Client
{
public int ClientID { get; set; }
public string ClientName { get; set; }
public string ClientPhone { get; set; }
public City ClientCity { get; set; }
}
public class City
{
public int CityID { get; set; }
public string CityName { get; set; }
public Country CityCountry { get; set; }
}
public class Country
{
public int ContryID { get; set; }
public string CountryName { get; set; }
}
So, if I have a Client object, I would get its country name by the expression client.ClientCity.CityCountry.CountryName. I call it a 3-level property acessor.
And I want to reflect it properly. Here is the main method to transform the DataTable into a List. My native language is Portuguese, but I tried to translate my comments to match my description above.
The idea of this code is: I try to find in the main class the column I have to set. If I don't find it, I search the property in the properties that are objects. Like CityName inside ClientCity inside Client. This code is a mess.
public List<T> ToList<T>(DataTable dt) where T : new()
{
Type type= typeof(T);
ReflectionHelper h = new ReflectionHelper(type);
insertPropInfo(tipo); //a pre-reflection work, I cache some delegates, etc..
List<T> list = new List<T>();
DataTableReader dtr = dt.CreateDataReader();
while (dtr.Read())
{
T obj = new T();
for (int i = 0; i < dtr.FieldCount; i++)
{
GetObject(ref obj, tipo, dtr.GetName(i), dtr.GetValue(i));
}
list.Add(obj);
}
return lista;
}
//ref T obj: the object I create before calling this method
//Type classType: the type of the object (say, Client)
//string colName: this is the Database Column i'm trying to fill. Like ClientID or CityName or CountryName.
//colLineData: the data I want to put in the colName.
public void GetObject<T>(ref T obj, Type classType, string colName, object colLineData) where T : new()
{
//I do some caching to reflect just once, and after the first iteration, I think all the reflection I need is already done.
foreach (PropertyInfo info in _classPropInfos[classType])
{
//If the current PropertyInfo is a valuetype (like int, int64) or string, and so on
if (info.PropertyType.IsValueType || info.PropertyType == typeof(string))
{
//I think string.Equals is a little faster, but i had not much difference using "string" == "string"
if (info.Name.Equals(colName)) //did I found the property?
if (info.PropertyType != typeof(char)) //I have to convert the type if this is a Char. MySql returns char as string.
{
_delegateSetters[info](obj, colLineData); //if it isn't a char, just set it.
}
else
{
_delegateSetters[info](obj, Convert.ChangeType(colLineData, typeof(char)));
}
break;
}
else //BUT, if the property is a class, like ClientCity:
{
//I reflect the City class, if it isn't reflected yet:
if (!_classPropInfos.ContainsKey(info.PropertyType))
{
insertPropInfo(info.PropertyType);
}
//now I search for the property:
Boolean foundProperty = false;
object instance = _delegateGetters[info](obj); //Get the existing instance of ClientCity, so I can fill the CityID and CityName in the same object.
foreach (PropertyInfo subInfo in _classPropInfos[info.PropertyType])
{
if (subInfo.Name.Equals(colName))//did I found the property?
{
if (instance == null)
{
//This will happen if i'm trying to set the first property of the class, like CityID. I have to instanciate it, so in the next iteration it won't be null, and will have it's CityID filled.
instance = _initializers[info.PropertyType]();//A very fast object initializer. I'm worried about the Dictionary lookups, but i have no other idea about how to cache it.
}
_delegateSetters[subInfo](instance, colLineData);//set the data. This method is very fast. Search about lambda getters & setters using System.Linq.Expression.
foundProperty = true;
break;//I break the loops when I find the property, so it wont iterate anymore.
}
}
if (foundProperty)//if I found the property in the code above, I set the instance of ClientCity to the Client object.
{
_delegateSetters[info](obj, instance);
break;
}
}
}
}
There is a problem with this code: I can reach the CityID and CityName, and fill it. But CountryID and CountryName wont. Because this code can do a 2-level reflection, I need some recursive-approach to fill many levels I need. I tried to do this BUT i got so many stack overflows and null reference exceptions I almost gave up.
This code would make it much easier to fetch database rows, Did you already find some library or anything that does what I want? If not, how could I achieve a n-level reflection to make a proper List from a DataTable?
Your problem is really common and practically every ORM in circulation addresses this question.
Of course changing an already written application to take advantage of an ORM is often unpractical, but there are some simple ORM that are really easy to add to an existing application and let you replace incrementally the already written code.
One of these ORMs is DAPPER. It consists of just one source file that you can include directly in the same project with your POCO classes and repository methods (Or just reference the compiled assembly). It is really easy to learn and it is incredibly fast considering the complexity of the work to be carried out. Not to mention that the authors of this little gem are regularly on this site answering questions on their work. Just do a search with the #dapper tag
The only nuisances that I have found to date are the mapping one-to-one from your POCO properties and the field names and also the sometime eluding rules between PK and FK when your keys are not named ID. But that's me that I still haven't fully understood these rules.
Consider to use EntityFramework. It will automate all this work.
This is based on you getting a dataset with the 3 tables and creating the proper DataRelation.
On your particular case(200k lines) i dont know how it will perform but shouldnt be that bad :).
Your calling code could be something like this:
List<Clients> clients = Test.CreateListFromTable<Clients>(ds.Tables["Clients"]);
Remember as i said its based in you fettching the dataset and creating the relations.
Next here is the class with the methods in question(ClientsToCity and CityToCountry are the names of the datarelations,you can place your own):
public class Test
{
// function that set the given object from the given data row
public static void SetItemFromRow<T>(T item, DataRow row) where T : new()
{
foreach (DataColumn c in row.Table.Columns)
{
PropertyInfo prop = item.GetType().GetProperty(c.ColumnName);
if (prop != null && row[c] != DBNull.Value)
{
prop.SetValue(item, row[c], null);
}
else
{
if (c.ColumnName == "CityID")
{
object obj = Activator.CreateInstance(typeof(City));
SetItemFromRow<City>(obj as City, row.GetChildRows("ClientsToCity")[0]);
PropertyInfo nestedprop = item.GetType().GetProperty("ClientCity");
nestedprop.SetValue(item, obj, null);
}
else if (c.ColumnName == "CountryID")
{
object obj = Activator.CreateInstance(typeof(Country));
SetItemFromRow<Country>(obj as Country, row.GetChildRows("CityToCountry")[0]);
PropertyInfo nestedprop = item.GetType().GetProperty("CityCountry");
nestedprop.SetValue(item, obj, null);
}
}
}
}
// function that creates an object from the given data row
public static T CreateItemFromRow<T>(DataRow row) where T : new()
{
T item = new T();
SetItemFromRow(item, row);
return item;
}
// function that creates a list of an object from the given data table
public static List<T> CreateListFromTable<T>(DataTable tbl) where T : new()
{
List<T> lst = new List<T>();
foreach (DataRow r in tbl.Rows)
{
lst.Add(CreateItemFromRow<T>(r));
}
return lst;
}
}
I have some collection List<ObjectId> called 'ids'. I want to allow users to create flexible filters for 'ids'.
I would like to create a textbox, into which user must to write a filter string. For example:
item => item.ClassName == "Rectangle"
This expression I need apply for each item on 'ids'. How can I try parse it for validation? How can I run it in my code for each 'ids' item?
You can take advantage of Dynamic LINQ
Why don't you create a class that extends List, then you can create get method like:
public class ObjectIdCollection : List<ObjectId>
{
public ObjectIdCollection() { }
public ObjectId this[string classname]
{
get
{
foreach(ObjectId id in this) if(id.ClassName == classname) return id;
return null;
}
}
}
This will either return the first Id with the classname specified, or null if none is found
They are many lambda expression parsers available.
Use the below logic to parse and execute the based on textbox (Say txtFilter) filter string.
var ids = new List<ObjectId>
{
new ObjectId { ClassName = "Rectangle1", ID = 1 },
new ObjectId { ClassName = "Rectangle2", ID = 2 }
};
string code = txtFilter.Text; (Ex: "item => item.ClassName == \"Rectangle1\"" ;)
Func<ObjectId, bool> func = ExpressionParser.Compile<Func<ObjectId, bool>>(code);
ids.ForEach(obejctId =>
{
Console.WriteLine(func.Invoke(obejctId));
});
Output is
True
False
I have a class
public class Orders
{
public Orders() {}
private string _idOrder;
private string _totalPrice;
public string idOrder
{
get{ return _idOrder;}
set { _idOrder = value;}
}
public string totalPrice
{
get { return _totalPrice; }
set { _totalPrice = value; }
}
}
I am loading the list from database like this
while (dr.Read())
{
Orders.idOrder = dr["IdOrder"].ToString();
Orders.totalPrice= dr["totalPrice"].ToString();
}
It's is showing me only last record. How can I load all the orders and retrieve them back by foreach loop?
Create a list :-)
List<Order> orders = new List<Order>();
while (dr.Read())
{
Order order = new Order();
order.idOrder = dr["IdOrder"].ToString();
order.totalPrice= dr["totalPrice"].ToString();
orders.Add(order);
}
As you see, I renamed your class from Orders to Order, because that's what it really represents: One order. To have more orders, you need to put those single orders into a list.
It's only showing you the one item, because you're only changing properties on the one item, not instantiating a new one:
var results = new List<Order>();
while (reader.Read())
{
var order = new Order
{
Id = (int)reader["IdOrder"],
TotalPrice = (decimal)reader["totalPrice"]
};
results.Add(order);
}
I think you are looking for something like this:
IEnumberable<Order> FetchOrders()
{
while(dr.Read())
yield return new Order {
idOrder=dr["IdOrder"].ToString(),
totalPrice=dr["totalPrice"].ToString()
});
}
That Orders class represents a single order! If what you need is a list of orders then I suggest you rename that class to Order, and then create a List<Order> (a list of order-objects) and populate that from your query results.
Also (forgive me for being pernickety) "idOrder" is not a good field name. The standard approaches are "orderId" or just plain old "Id" (ID, or even id). Likewise I would expect the price-of-ONE-order to be called just "amount", or even "price"... not "totalPrice"... it'll be too confusing when you come to total-up the totalPrices... get my drift?
Cheers. Keith.
I don't see how that will compile. Orders.idOrder is not a static property, it's an instance property.
If i understand you right you want to use something like this:
List<Order> = new List<Order>();
while (dr.Read())
{
Order newOrder = new Order();
newOrder.idOrder = dr["IdOrder"].ToString();
newOrder.totalPrice= dr["totalPrice"].ToString();
orderList.Add(newOrder);
}
Notice this that I just discuss more for #Grook Answer. I Think it is so near to what to want.
IEnumberable<Order> FetchOrders()
{
while(dr.Read())
yield return new Order {
idOrder=dr["IdOrder"].ToString(),
totalPrice=dr["totalPrice"].ToString()
});
}
Then You can easily use foreach loop
Foreach(Order order in GetOrders())
{
doSomething(order);
}
Is it clear?
This is tricky to explain.
We have a DataTable that contains a user configurable selection of columns, which are not known at compile time. Every column in the DataTable is of type String. We need to convert this DataTable into a strongly typed Collection of "ReturnItem" objects so that we can then sort and filter using LINQ for use in our application.
We have made some progress as follows:
We started with the basic DataTable.
We then process the DataTable, creating a new "ReturnItem" object for each row
This "ReturnItem" object has just two properties: ID ( string ) and Columns( List(object) ). The properties collection contains one entry for each column, representing a single DataRow.
Each property is made Strongly Typed (int, string, datetime, etc). For example it would add a new "DateTime" object to the "ReturnItem" Columns List containing the value of the "Created" Datatable Column.
The result is a List(ReturnItem) that we would then like to be able to Sort and Filter using LINQ based on the value in one of the properties, for example, sort on "Created" date.
We have been using the LINQ Dynamic Query Library, which gets us so far, but it doesn't look like the way forward because we are using it over a List Collection of objects.
Basically, my question boils down to: How can I use LINQ, to Sort and Filter items in a List(ReturnItem) collection, based on the values within a List(object) property which is part of the ReturnItem class?
I'm not sure I'm understanding what the problem is. Assuming you know the index of the column you want to sort on and they are fairly trivial types, can't you just do something like...
void Main()
{
List<ReturnItem> items = new List<ReturnItem>();
items.Add(new ReturnItem()
{
ID = 1,
Columns = new List<object>()
{
DateTime.Now,
"donkey"
}
});
items.Add(new ReturnItem()
{
ID = 2,
Columns = new List<object>()
{
DateTime.Now.AddHours(3),
"baboon"
}
});
items.Add(new ReturnItem()
{
ID = 3,
Columns = new List<object>()
{
DateTime.Now.AddHours(2),
"antelope"
}
});
IEnumerable<ReturnItem> sortedByDate =
items.OrderBy(x => x.Columns[0]);
IEnumerable<ReturnItem> sortedByAnimal =
items.OrderBy(x => x.Columns[1]);
IEnumerable<ReturnItem> filteredByBaboon =
items.Where(x => x.Columns[1] == "baboon");
}
public class ReturnItem
{
public int ID;
public List<object> Columns;
}
Test this.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
List<ReturnItem> list = new List<ReturnItem>();
list.Add(new ReturnItem(1, "mouse", DateTime.Now));
list.Add(new ReturnItem(2, "mickey",DateTime.Now));
list = list.OrderBy(i => i._column._name).ToList();
list.ForEach(i => Console.WriteLine(i._column._name));
Console.Read();
}
}
class ReturnItem
{
public int _id;
public Columns _column;
public ReturnItem(int id, string name, DateTime date)
{
_id = id;
_column = new Columns(name, date);
}
}
class Columns
{
public string _name;
public DateTime _date;
public Columns(string name, DateTime date)
{
_name = name;
_date = date;
}
}
}