Convert a DataTable to IList - linq

I am updating some legacy that has a Table that only contains one row. I want to convert it to an IList. The idea is that the first property of IFoo is Name which would be the column name from the Table and the second if Value which is the value of the column.
/// <summary>
/// Name = Column Name
/// </summary>
String Name { get; set; }
/// <summary>
/// Value = Column Value
/// </summary>
String Value { get; set; }
The data may look something this
Foo1 Foo2 Foo3 Foo4 Foo5
xyz zyx abc def ghi
And would be:
Foo1, xyz
Foo2, zyx
Foo3, abc
Foo4, def
Foo5, ghi
I am not really sure how to accomplish this. Seems like there could be a linq query to do it. Any help would be appreciated.
Rhonda

Perhaps (works also if the table contains more rows):
IList<Foo> data = table.AsEnumerable()
.SelectMany(r => table.Columns.Cast<DataColumn>()
.Select(col => new Foo
{
Name = col.ColumnName,
Value = r.IsNull(col) ? null : r[col].ToString()
}))
.ToList();

Looks like classic SQL columns to rows task. I've added column ID so you can use this query for more than one row:
var dt = new DataTable();
dt.Columns.Add("ID");
dt.Columns.Add("Foo1");
dt.Columns.Add("Foo2");
dt.Rows.Add(1, "xyz", "abc");
dt.Rows.Add(2, "cds", "tgf");
var q = from row in dt.Rows.Cast<DataRow>()
from col in dt.Columns.Cast<DataColumn>()
select new {
ID = row["ID"],
Name = col.ColumnName,
Value = row.IsNull(col) ? null : row[col]
};
q.ToList()
ID, Name, Value
1 Foo1 xyz
1 Foo2 abc
2 Foo1 cds
2 Foo2 tgf
or, if you want to create a Dictionary
q.ToDictionary(x => new { x.ID, x.Name}, x => x.Value)

Related

Conversion failed when converting the varchar value 'Zon7' to data type int

I'm getting this error :
Conversion failed when converting the varchar value 'Zon7' to data
type int.
establishments = GetEstablishments(waters.Select(t => ReplaceZonToEmptyString(t.IdGeographie)));
public static int ReplaceZonToEmptyString(string zoneId)
{
zoneId.Replace("Zon", string.Empty);
var sbInput = zoneId.Replace(" ", string.Empty);
return Convert.ToInt32(sbInput.ToString());
}
public static IQueryable<Etablissement> GetEstablishments(IQueryable<int> ids)
{
return from zone in entities.Zones
where ids.Contains(zone.IdZone)
select zone.IdBatimentNavigation.IdEtablissementNavigation;
}
var result = establishments.ToList();
in database i have a column of type varchar the column name is 'IdGeographie' with values that start with 'Zon', something like this "ZonXXXX"
You are trying to compare a VARCHAR column with values of type int. One of these values will have to change and since it con not be the SQL column, it has to be the compare value:
public static string ReplaceZonToEmptyString(string zoneId)
{
var sbInput = new StringBuilder(zoneId);
sbInput.Replace("Zon", string.Empty);
sbInput.Replace(" ", string.Empty);
return sbInput.ToString();
}
public static IQueryable<Etablissement> GetEstablishments(IQueryable<string> ids)
{
return from zone in entities.Zones
where ids.Contains(zone.IdZone)
select zone.IdBatimentNavigation.IdEtablissementNavigation;
}
If the signature of the methods can't change, you have to do the conversion within GetEstablishments:
public static IQueryable<Etablissement> GetEstablishments(IQueryable<int> ids)
{
var textIds = ids.Select(id => id.ToString());
return from zone in entities.Zones
where textIds.Contains(zone.IdZone)
select zone.IdBatimentNavigation.IdEtablissementNavigation;
}
Note that in waters.Select(t => ReplaceZonToEmptyString(t.IdGeographie)), the value waters must be a materialized list of values (i.e. not another EF query), since your replace operation can not work within Entity Framework (in either of the options).
What do you think the following code will do:
string original = "Hello World!";
string changed = original.Recplace("o", "xx");
original won't be changed, changed will equal "Hellxx, Wxxrld".
So you should change your ReplaceZonToEmptyString:
public static int ExtractZoneNr(string zoneId)
{
string idWithoutZon = zoneId.Replace("Zon", string.Empty);
string sbInput = idWithoutZon.Replace(" ", string.Empty);
return Int32.Parse(sbInput);
}
Alas this will only work on IEnumerable, not on IQueryable
A better solution:
This solution only works if IdGeographie is the three letters "Zon" followed by the string representation of the IdZone and nothing else. So no spaces, no leading zeroes etc: "Zon4" and not "Zon004", nor "Zon 4"
You have two IQueryables one for waters and one for zones:
IQuerybale<Water> waters = ... // probably entities.Waters
IQueryable<Zone> zones = entities.Zones
Every Zone contains an int property IdZone, and a property .IdBatimentNavigation.IdEtablissementNavigation, which seems to be of type Etablissement
Furhermore every Water has a string property GeographieId in the format "ZonX" where X is an integer number.
Now you want to query the IdBatimentNavigation.IdEtablissementNavigation of all Zones with an IdZone that equals the X part of one or more of the Waters
For example: if you have the following Waters
[0] GeographieId = "Zon10"
[1] GeographieId = "Zon42"
[2] GeographieId = "Zon7"
And you have Zones with IdZone: 4, 42, 30, 7, 22.
Then as a result you want the IdBatimentNavigation.IdEtablissementNavigation of the Zones with IdZone: 42 any 7 (in any order)
Why not join waters and zones?
var result = zones.
.Select(zone => new
{
GeographieId = "Zon" + zone.IdZone.ToString(),
Etablissement = zoneIdBatimentNavigation.IdEtablissementNavigation,
})
.Join(waters, // join with waters
zone => zone.GeographieId, // from zone take the GeoGraphieId
water => water, // from waters take idGeographie
(zone, water) => zone.Etablissement); // when thay match return
A better solution would be to try to remove the "Zon" part from IdGeographie, and Parse the remaining XXXX to an int. Alas there is no function that can perform the parsing AsQueryable.

Getting a column by string name

I'm trying to update a record given the customer Id, the row Id, and a dynamic column name.
Thus far I have the following, with the trouble spot marked by ***:
public void UpdateRecord(int Id, string rval, string column, string value)
{
var rId = GetRvalId(rval);
var entry = _context.Customers
.Where(x => x.Id == Id && x.RVals.Id == rId && x.***column?*** == column).First();
entry = value;
}
I haven't been able to find a good example of how to do this.
Addition after comments at the end
The reason you couldn't find examples is because it is not a good design.
Your method is very error prone, difficult to test and horrible to maintain. What if someone types the incorrect column name? What if you try to assign a string to the customer's birthday? And even if you would implement some string checking for column names and proposed values, then your program wouldn't work anymore after someone changes the names or the types of the columns.
So let's redesign!
Apparently you have a Customer with an Id and a property Rvals. This property Rvals also has a property Id.
You also have a function GetRValId that can convert a string rval to an int rvalId.
What you want, is given an Id and a string rval, you want to update one of the columns of the first Customer with this Idand rValId.
Side questions: Can there be more than one Customer with Id? In that case: are you sure Id is an ID? What do you want if there are more matching Customers? Update all customers or update only the first one? Which customer do you define as the first customer?
Leaving the side questions aside. We want a function signature that reports errors at compile time if you use non-existing customer properties, or if you try to assign a string to a Birthday. Something like this perhaps?
Update the name of the customer:
int customerId = ...
string rval = ...
string proposedName = "John Doe";
UpdateCustomerRecord(id, rval, customer => customer.Name = proposedName);
Update the Birthday of the customer:
DateTime proposedBirthday = ...
UpdateCustomerRecord(id, rval, customer => customer.Birthday = proposedBirthday)
This way you can't use any column that does not exist, and you can't assign a string to a DateTime.
You want to change two values in one call? Go ahead:
UpdateCustomerRecord(id, rval, customer =>
{
customer.Name = ...;
customer.Birthday = ...;
});
Convinced? Let's write the function:
public void UpdateCustomerRecord(int customerId, string rval, Action<Customer> action)
{
// the beginning is as in your function:
var rId = GetRvalId(rval);
// get the customer that you want to update:
using (var _Context = ...)
{
// get the customer you want to update:
var customerToUpdate = _Context.Customers
.Where(customer => customer.Id == Id
&& customer.RVals.Id == rId)
.FirstOrDefault();
// TODO: exception if there is no customerToUpdate
// perform the action and save the changes
action(customerToUpdate);
_context.SaveChanges();
}
Simple comme bonjour!
Addition after comments
So what does this function do? As long as you don't call it, it does nothing. But when you call it, it fetches a customer, performs the Action on the Customer you provided in the call, and finally calls SaveChanges.
It doesn't do this with every Customer, no it does this only with the Customer with Id equal to the provided Id and customer.RVals.Id == ... (are you still certain there is more than one customer with this Id? If there is only one, why check for RVals.Id?)
So the caller not only has to provide the Id, and the RVal, which define the Customer to update, but he also has to define what must be done with this customer.
This definition takes the form of:
customer =>
{
customer.Name = X;
customer.BirthDay = Y;
}
Well if you want, you can use other identifiers than customer, but it means the same:
x => {x.Name = X; x.BirthDay = Y;}
Because you put it on the place of the Action parameter in the call to UpdateCustomerRecord, I know that x is of type Customer.
The Acton statement means: given a customer that must be updated, what must we do with the customer? You can read it as if it was a Function:
void Action(Customer customer)
{
customer.Name = ...
customer.BirthDay = ...
}
In the end it will do something like:
Customer customerToUpdate = ...
customerToUpdate.Name = X;
customerToUpdate.BirthDay = Y;
SaveChanges();
So in the third parameter, called Action you can type anything you want, even call functions that have nothing to do with Customers (probably not wise). You have an input parameter of which you are certain that it is a Customer.
See my earlier examples of calling UpdateCustomerRecord, one final example:
UpdateCustomerRecord( GetCustomerId(), GetCustomerRVal,
// 3rd parameter: the actions to perform once we got the customerToUpdate:
customer =>
{
DateTime minDate = GetEarliestBirthDay();
if (customer.BirthDay < minDate)
{ // this Customer is old
customer.DoThingsThatOldPeopleDo();
}
else
{ // this Customer is young
customer.DoThingsThatYoungPeopleDo();
}
}
}
So the Action parameter is just a simpler way to say: "once you've got the Customer that must be updated, please perform this function with the Customer
So if you only want to update a given property of the customer write something like:
UpdateCustomerRecord(... , customer =>
{
Customer.PropertyThatMustBeUpdated = NewValueOfProperty;
}
Of course this only works if you know which property must be updated. But since you wrote "I am trying to update a specific cell." I assume you know which property the cells in this column represent.
It is not possible to pass the column name as the string value in LINQ. Alternate way to do it, if you have the limited number of the column name which can be passed then it can be achieved as below:
public void UpdateRecord(int Id, string rval, string column, string value)
{
var rId = GetRvalId(rval);
var entry = _context.Customers
.Where(x => x.Id == Id &&
x.RVals.Id == rId &&
(x.column1 == value || column == column1) &&
(x.column2 == value || column == column2) &&
(x.column3 == value || column == column3) &&
(x.column4 == value || column == column4) &&
(x.column5 == value || column == column5) &&
)).First();
entry = value;
}
UpdateRecord(5, "rval", "column1", "value");
UpdateRecord(5, "rval", "column2", "value");
UpdateRecord(5, "rval", "column3", "value");
Here, suppose you have the 5 columns that can be passed while calling the funcion UpdateRecord then you can add the 5 clauses in the WHERE as above.
Other way to do it dynamic LINQ
var entry = db.Customers.Where(column + " = " + value).Select(...);

Intersect or union with a custom IEqualityComparer using Linq

I have 2 set of coolection in memory and i want to return one set based on the 2. My object have the following suructure:
class Item
{
public string key {get,set;}
public int total1 {get;set;}
public int total2 {get ;set;}
}
I would like to "union" them so that when the key on item form set 1 is equal to the key of an item from the set 2 , my union should return an item as follow:
item_union.Key= item1.key==item2.key;
item_union.total1= item1.total1 + item2.total1;
item_union.total2= item1.total2 + item2.total2;
can someone show me how i should build my custom equality compararer to obtain this result?
many thanks in advance
It sounds like you might want a join, or you might just want to concatenate the collections, group by the key and then sum the properties:
// Property names changed to conform with normal naming conventions
var results = collection1.Concat(collection2)
.GroupBy(x => x.key)
.Select(g => new Item {
Key = g.Key,
Total1 = g.Sum(x => x.Total1),
Total2 = g.Sum(x => x.Total2)
});

LINQ: Drill down to value in query results

I am using LINQ for the first time and have the following query:
var query = from c in context.Call
join d in context.CallData on c.CallID = d.CallID
where c.CallID == 123
select new {
c.CallID,
c.CallResult,
d.FieldID,
d.FieldValue
};
This returns data similar to the following
c.CallID c.CallResult d.FieldID d.FieldValue
123 Sale 345 John
123 Sale 346 Doe
123 Sale 347 888-222-3333
How can drill down to the row containing d.FieldID = 346 to get the value of d.FieldValue (346 being the LastName field)? I would like to use this value as the new value of a local variable:
// I know this doesn't work, just showing what I would like to accomplish
string LastName = query.Select(a => a.FieldID = 346).FieldValue;
Conversely, how can I change the value of c.CallResult once I have these results? Since it is a join, can I simply change one row of data or do I have to do a separate query?
For the first question you can move on from your query variable:
var fieldValue = query.Where(x => x.FieldID == 346).Single().FieldValue;
As for your second question: you must apply a change to the original entity object:
var call = context.Find(123);
// or context.Calls.Single(c => c.CallId == 123) if this is not DbContext.
call.CallResult = newValue;
context.SaveChanges();
Do you mean:
string LastName = query
.Where(a => a.FieldID = 346)
.Select(a => a.FieldValue)
.FirstOrDefault();

How to Update previous row column based on the current row column data using LinQ

var customer= from cust in customerData
select new Customer
{
CustomerID = cust["id"],
Name = cust["Name"],
LastVisit = cust["visit"],
PurchashedAmount = cust["amount"],
Tagged = cust["tagged"]
Code = cust["code"]
}
The rows looks like this
Name LastVisit PurchasedAmount Tagged Code CustomerID
------ --------- -------------- ------ ----- -----
Joshua 07-Jan-09 Yes chiJan01 A001
Joshua 10000
The 2nd row belongs to first row just that the other columns are empty.How can i merge the PurchasedAmount into the first row using LinQ?
This is probably a more general solution than you need - it will work even if the other values are scattered across rows. The main condition is that the Name column should identify rows that belong together.
customer = from c in customer
group c by c.Name
into g
select new Customer
{
Name = g.Key,
LastVisit = g.Select(te => te.LastVisit).
Where(lv => lv.HasValue).FirstOrDefault(),
PurchaseAmount = g.Select(te => te.PurchaseAmount).
Where(pa => pa.HasValue).FirstOrDefault(),
Tagged = g.Select(te => te.Tagged).
Where(ta => ta.HasValue).FirstOrDefault(),
Code = g.Select(te => te.Code).
Where(co => !string.IsNullOrEmpty(co)).FirstOrDefault(),
CustomerID = g.Select(te => te.CustomerID).
Where(cid => !string.IsNullOrEmpty(cid)).FirstOrDefault()
};
This will return a new IEnumerable with the items grouped by Name and the non-null values selected (same effect as moving PurchasedAmount to the first row and deleting the second in your case).
Note that the code is based on the assumption that LastVisit, PurchaseAmount and Tagged are nullable types (DateTime?, int? and bool?). Thus the usage of HasValue. If, however, they are strings in your case, you have to use !string.IsNullOrEmpty() instead (as for Code and CustomerID).

Resources