Update a datatable with reference key from another datatable - c#

I have one datatable, say 'dtEmp', with columns [EmployeeID][CompanyID][CompanyName]
and another say 'dtCompany' with columns [CompanyID][CompanyName]
I want to update 'dtEmp' with respective Company names in column [CompanyName]
Plese guide. I tried searching this but I could not find exact words to search :(

How about
for(int i = 0; i < dtCompany.Rows.Count; i++)
{
for(int j = 0 ; j < dtEmp.Rows.Count ; j++)
{
if (Convert.ToString(dtCompany.Rows[i]["CompanyID"]) ==
Convert.ToString(dtEmp.Rows[j]["CompanyID"]))
{
dtEmp.Rows[j]["CompanyName"] =
Convert.ToString(dtCompany.Rows[i]["CompanyName"]);
}
}
}

var result = (from t1 in dtEmp
join t2 in dtCompany on t1.CompanyID equals t2.CompanyID
select new { t1.EmployeeID, t1.CompanyID, t2.CompanyName}).ToList()
I don't know what your long term purpose is for updating the datatable, however using something like this would provide you a list of an anonymous object which would contain the 3 fields that you needed. You could potentially hard cast these to strongly typed Datarows and then create a brand new table (or update your existing one) with each row.

Linq itself cannot do update.
Assuming Typed DataTable, simply:
foreach(var rowEmp in dtEmp)
{
var rowComp = dtCompany
.Where(r => r.CompanyID == rowEmp.CompanyID)
.FirstOrDefault();
if(rowComp == null)
rowEmp.SetCompanyNameNull();
else
rowEmp.CompanyName = rowComp.CompanyName;
}
(Ignoring the case of the content having null or DBNull, do null-check if required please.)
Similar logic is applicable to general DataTable.

Related

Renumbering database rows is inefficient

My database has a Task table with a Sequence column. The Sequence column specifies the order of the tasks.
In some cases, I need to change the order. So I would use something like this:
var tasks = (dbContext.Tasks
.Where(t => t.UserId == userId)).ToList();
for (int i = 0; i < tasks.Count; i++)
{
// Set new sequence
tasks[i].Sequence = i;
}
dbContext.SaveChanges();
This seems rather inefficient to have to retrieve every column of every Task in the set.
Is there a more efficient way to do this?
Note: Please don't get caught up in the fact that I'm simply setting Sequence to i in the code above. The real code will have data receive data that indicates the correct values. But if I could optimize the code above, I could then adapt it to my final needs.
You should be able to pull down only the column you want to update by using a Select statement, and then, according to this answer, update just that column.
This example might work, but unfortunately I can't test it right now:
// Query just a single column
var tasks = dbContext.Tasks
.Where(t => t.UserId == userId)
.Select(t => new Task { UserId = t.UserId, Sequence = t.Sequence })
.ToList();
// Update a single column and tell EF to track it
for(int i = 0; i < tasks.Count; i++)
{
tasks[i].Sequence = i;
dbContext.Attach(tasks[i]);
dbContext.Entry(tasks[i]).Property(t => t.Sequence).IsModified = true;
}
// Save the changes to that column
dbContext.SaveChanges();

Group and flatten a query into a variable number of fields

I've got three tables, one for workers, another one for folders and a third one that combines IDs from workers and folder indicating that the worker has access to the folder.
I need to fill a table representing whether the workers have access or not to the folders.
So far I've come up with this:
var accessQuery = from folder in db.FOLDERS
from worker in db.WORKERS
select new
{
Folder = folder.Name,
Worker = worker.Name,
Access = worker.FolderAccess.Count(f => f.ID_Folder == folder.ID_Folder) > 0
};
I'd use another query to get all the Folders and then create a DataTable where the first column would be the worker's name and then add each folder as a new column.
Then I'd iterate through each row to fill a DataTable with the access data. That DataTable I use to feed a GridView or to export the data.
I'm wondering whether there is a way to accomplish this with just one single query or at least a more efficient way as mine doesn't seem efficient at all.
I ended coming with a solution. Not sure if it's the best (probably not) but it's better than what I was using. Thanks to Ako for point the 'Any' change as it seems better than the count.
Here's the whole code with the DataTable filling
//First I get a list of all the folders
var queryFolders = from f in db.FOLDERS
orderby f.Name
select new
{
f.Name
};
//Here there's the query I was looking for
var queryAccess = from f in db.FOLDERS
from u in db.USERS
orderby f.Name
select new
{
User = u.Name,
Access = u.FolderAccess.Any(x => x.ID_Folder == f.ID_Folder)
}
into crossJoin
group crossJoin by new { crossJoin.User } into results
select new
{
results.Key.User,
AccessList = results.Select(x => x.Access).ToArray()
};
//Now I wrap the queries into a DataTable so I can easily feed them to what I need
DataTable dtAccess = new DataTable();
dtAccess.Columns.Add("User");
foreach (var f in queryFolders)
{
dtAccess.Columns.Add(f.Name, typeof(bool));
}
foreach (var a in queryAccess)
{
DataRow userFolders = dtAccess.NewRow();
userFolders["User"] = a.User;
for (int i = 1; i <= a.AccessList.Length; i++)
{
userFolders[i] = a.AccessList[i - 1];
}
dtAccess.Rows.Add(userFolders);
}
Use join. FYI this is basic RDBMS sql query so imho you better learn sql query first before moving to linq2sql.
var accessQuery = from worker in db.WORKERS
join access in db.ACCESS on access.ID_Worker == worker.ID_Worker
join folder in db.FOLDERS on access.ID_Folder == folder.ID_Folder
select new
{
Folder = folder.Name,
Worker = worker.Name
};

LINQ: Add RowNumber Column

How can the query below be modified to include a column for row number (ie: one-based index of results)?
var myResult = from currRow in someTable
where currRow.someCategory == someCategoryValue
orderby currRow.createdDate descending
select currRow;
EDIT1: I'm looking for the results to be {idx, col1, col2...col-n} not {idx, row}.
EDIT2: The row number should correspond to result rows not the table rows.
EDIT3: I DataBind these results to a GridView. My goal was to add a row number column to the GridView. Perhaps a different approach would be better.
Use the method-syntax where Enumerable.Select has an overload with the index:
var myResult = someTable.Select((r, i) => new { Row = r, Index = i })
.Where(x => x.Row.someCategory == someCategoryValue)
.OrderByDescending(x => x.Row.createdDate);
Note that this approach presumes that you want the original index of the row in the table and not in the filtered result since i select the index before i filter with Where.
EDIT: I'm looking for the results to be {idx, col1, col2...col-n} not
{idx, row}. The row number should correspond to result rows not
the table rows.
Then select the anonymous type with all columns you need:
var myResult = someTable.Where(r => r.someCategory == someCategoryValue)
.OrderByDescending(r => r.createdDate)
.Select((r, i) => new { idx = i, col1 = r.col1, col2 = r.col2, ...col-n = r.ColN });
Use this Select method:
Projects each element of a sequence into a new form by incorporating the element's index.
Example:
var myResult = someTable.Where(currRow => currRow.someCategory == someCategoryValue)
.OrderByDescending(currRow => currRow.createdDate)
.Select((currRow, index) => new {Row = currRow, Index = index + 1});
In response to your edit:
If you want a DataTable as result, you can go the non-Linq way by simply using a DataView and add a additional column afterwards.
someTable.DefaultView.RowFilter = String.Format("someCategory = '{0}'", someCategoryValue);
someTable.DefaultView.Sort = "createdDate";
var resultTable = someTable.DefaultView.ToTable();
resultTable.Columns.Add("Number", typeof(int));
int i = 0;
foreach (DataRow row in resultTable.Rows)
row["Number"] = ++i;
what about?
int i;
var myResult = from currRow in someTable
where currRow.someCategory == someCategoryValue
orderby currRow.createdDate descending
select new {Record = i++, currRow};
Just for fun, here's an alternative to Select with two arguments:
var resultsWithIndexes = myResult.Zip(Enumerable.Range(1, int.MaxValue - 1),
(o, i) => new { Index = i, Result = o });
According to you edit 1. NO, YOU CAN'T Linq returns the table as it is. You can build each column, but you lose the power of mapped entities.
This has been asked multiple times before: How do you add an index field to Linq results
There is no straightforward way if want to keep a flat list of columns (i.e. OP's Edit2) and also want a generic solution that works with any IEnumerable without requiring you to list out the set of expected columns.
However, there is a roundabout way to kinda go about it which is to dump the query results into a DataTable using the ToDataTable() method from here and then add a RowNumber column to that table.
var table = query.ToList().ToDataTable();
table.Columns.Add("RowNum", typeof(int));
int i = 0;
foreach (DataRow row in table.Rows)
row["RowNum"] = ++i;
This would likely cause performance issues with large datasets but it's not insanely slow either. On my machine a dataset with ~6500 rows took 33ms to process.
If your original query returned an anonymous type, then that type definition will get lost in the conversion so you'll lose the static typing on the column names of the resulting IEnumerable when you call table.AsEnumerable(). In other words, instead of being able to write something like table.AsEnumerable().First().RowNum you instead have to write table.AsEnumerable().First()["RowNum"]
However, if you don't care about performance and really want your static typing back, then you can use JSON.NET to convert the DataTable to a json string and then back to a list based on the anonymous type from the original query result. This method requires a placeholder RowNum field to be present in the original query results.
var query = (from currRow in someTable
where currRow.someCategory == someCategoryValue
orderby currRow.createdDate descending
select new { currRow.someCategory, currRow.createdDate, RowNum = -1 }).ToList();
var table = query.ToDataTable();
//Placeholder RowNum column has to already exist in query results
//So not adding a new column, but merely populating it
int i = 0;
foreach (DataRow row in table.Rows)
row["RowNum"] = ++i;
string json = JsonConvert.SerializeObject(table);
var staticallyTypedList = JsonConvert.DeserializeAnonymousType(json, query);
Console.WriteLine(staticallyTypedList.First().RowNum);
This added about 120ms to the processing time for my 6500 item dataset.
It's crazy, but it works.
I know I'm late to the party, but I wanted to show what worked for me.
I have a list of objects, and the object has an integer property on it for "row number"... or in this case, "Sequence Number". This is what I did to populate that field:
myListOfObjects = myListOfObjects.Select((o, i) => { o.SequenceNumber = i; return o; }).ToList();
I was surprised to see that this worked.
This one helped me in my case - Excel sheet extraction. anonymous type
var UploadItemList = ItemMaster.Worksheet().AsEnumerable().Select((x, index) => new
{
Code = x["Code"].Value == null ? "" : x["Code"].Value.ToString().Trim(),
Description = x["Description"].Value == null ? "" : x["Description"].Value.ToString().Trim(),
Unit = x["Unit"].Value == null ? "" : x["Unit"].Value.ToString().Trim(),
Quantity = x["Quantity"].Value == null ? "" : x["Quantity"].Value.ToString().Trim(),
Rate = x["Rate"].Value == null ? "" : x["Rate"].Value.ToString().Trim(),
Amount = x["Amount"].Value == null ? "" : x["Amount"].Value.ToString().Trim(),
RowNumber = index+1
}).ToList();
int Lc = 1;
var Lst = LstItemGrid.GroupBy(item => item.CategoryName)
.Select(group => new { CategoryName = group.Key, Items = group.ToList() ,RowIndex= Lc++ })
.ToList();

DateTime WHERE Parameters

I have a gridview that is populated from an entitydatasource in asp.net. In my c# code-behind I need to add a WHERE parameter to the entity data source that will filter out all data that is not >= validDate1 and <= validDate2.
Here is where I'm at:
using (RamRideOpsEntities myEntities = new RamRideOpsEntities())
{
var validDates = (from a in myEntities.AdminOptions
select new { a.ValidDate1, a.ValidDate2 }).FirstOrDefault();
if (validDates != null)
{
RidesGridView.Where = " ..... ??? " //TODO
}
}
Edit: with the answers below, let me be more clear.. the validDates = .... statement is just getting the two valid dates I need to filter by.. there 'where' clause needs to be added to the entity data source so the data displayed in the grid view is within the valid date range.
You're using an EntityDataSource. This is a very un-linq type which allows you to use runtime-defined strings to modify queries. A more linq-ish technique would prefer that the string is part of the language of the program, and the compiler would turn it into an expression tree (as others have posted). That linq-ish approach won't work as long as you're using an EntityDataSource.
From the msdn article referencing EntityDataSource.Where, it looks like you need to use the magic word "it" to describe the row. Then you'd supply the parameters to the .WhereParameters collection.
Code:
using (myEntities = new RamRideOpsEntities())
{
var validDates = (from a in myEntities.AdminOptions
select new { a.ValidDate1, a.ValidDate2 }).FirstOrDefault();
if (validDates != null)
{
RidesEDS.Where = #"it.TimeOfCall >= #ValidDate1 AND it.TimeOfCall <= #ValidDate2";
RidesEDS.WhereParameters.Add(#"ValidDate1", DbType.DateTime, validDates.ValidDate1.ToString());
RidesEDS.WhereParameters.Add(#"ValidDate2", DbType.DateTime, validDates.ValidDate2.ToString());
}
}
var validDates = (from a in myEntities.AdminOptions where
a.ValidDate1 >= validDate1 && a.ValidDate2 <= validDate2
select new { a.ValidDate1, a.ValidDate2 }).FirstOrDefault();
You don't filter the GridView, you filter the data source. The answer will depend on what kind of data source you have, but assuming it's Linq-compatible:
var items = dataSource.Where(
d => d.Date >= validDates.ValidDate1 && d.Date <= validDates.ValidDate2
);
RidesGridView.DataSource = items;

Filling in missing dates using a linq group by date query

I have a Linq query that basically counts how many entries were created on a particular day, which is done by grouping by year, month, day. The problem is that because some days won't have any entries I need to back fill those missing "calendar days" with an entry of 0 count.
My guess is that this can probably be done with a Union or something, or maybe even some simple for loop to process the records after the query.
Here is the query:
from l in context.LoginToken
where l.CreatedOn >= start && l.CreatedOn <= finish
group l by
new{l.CreatedOn.Year, l.CreatedOn.Month, l.CreatedOn.Day} into groups
orderby groups.Key.Year , groups.Key.Month , groups.Key.Day
select new StatsDateWithCount {
Count = groups.Count(),
Year = groups.Key.Year,
Month = groups.Key.Month,
Day = groups.Key.Day
}));
If I have data for 12/1 - 12/4/2009 like (simplified):
12/1/2009 20
12/2/2009 15
12/4/2009 16
I want an entry with 12/3/2009 0 added by code.
I know that in general this should be done in the DB using a denormalized table that you either populate with data or join to a calendar table, but my question is how would I accomplish this in code?
Can it be done in Linq? Should it be done in Linq?
I just did this today. I gathered the complete data from the database and then generated a "sample empty" table. Finally, I did an outer join of the empty table with the real data and used the DefaultIfEmpty() construct to deal with knowing when a row was missing from the database to fill it in with defaults.
Here's my code:
int days = 30;
// Gather the data we have in the database, which will be incomplete for the graph (i.e. missing dates/subsystems).
var dataQuery =
from tr in SourceDataTable
where (DateTime.UtcNow - tr.CreatedTime).Days < 30
group tr by new { tr.CreatedTime.Date, tr.Subsystem } into g
orderby g.Key.Date ascending, g.Key.SubSystem ascending
select new MyResults()
{
Date = g.Key.Date,
SubSystem = g.Key.SubSystem,
Count = g.Count()
};
// Generate the list of subsystems we want.
var subsystems = new[] { SubSystem.Foo, SubSystem.Bar }.AsQueryable();
// Generate the list of Dates we want.
var datetimes = new List<DateTime>();
for (int i = 0; i < days; i++)
{
datetimes.Add(DateTime.UtcNow.AddDays(-i).Date);
}
// Generate the empty table, which is the shape of the output we want but without counts.
var emptyTableQuery =
from dt in datetimes
from subsys in subsystems
select new MyResults()
{
Date = dt.Date,
SubSystem = subsys,
Count = 0
};
// Perform an outer join of the empty table with the real data and use the magic DefaultIfEmpty
// to handle the "there's no data from the database case".
var finalQuery =
from e in emptyTableQuery
join realData in dataQuery on
new { e.Date, e.SubSystem } equals
new { realData.Date, realData.SubSystem } into g
from realDataJoin in g.DefaultIfEmpty()
select new MyResults()
{
Date = e.Date,
SubSystem = e.SubSystem,
Count = realDataJoin == null ? 0 : realDataJoin.Count
};
return finalQuery.OrderBy(x => x.Date).AsEnumerable();
I made a helper function which is designed to be used with anonymous types, and reused in as generic way as possible.
Let's say this is your query to get a list of orders for each date.
var orders = db.Orders
.GroupBy(o => o.OrderDate)
.Select(o => new
{
OrderDate = o.Key,
OrderCount = o.Count(),
Sales = o.Sum(i => i.SubTotal)
}
.OrderBy(o => o.OrderDate);
For my function to work please note this list must be ordered by date. If we had a day with no sales there would be a hole in the list.
Now for the function that will fill in the blanks with a default value (instance of anonymous type).
private static IEnumerable<T> FillInEmptyDates<T>(IEnumerable<DateTime> allDates, IEnumerable<T> sourceData, Func<T, DateTime> dateSelector, Func<DateTime, T> defaultItemFactory)
{
// iterate through the source collection
var iterator = sourceData.GetEnumerator();
iterator.MoveNext();
// for each date in the desired list
foreach (var desiredDate in allDates)
{
// check if the current item exists and is the 'desired' date
if (iterator.Current != null &&
dateSelector(iterator.Current) == desiredDate)
{
// if so then return it and move to the next item
yield return iterator.Current;
iterator.MoveNext();
// if source data is now exhausted then continue
if (iterator.Current == null)
{
continue;
}
// ensure next item is not a duplicate
if (dateSelector(iterator.Current) == desiredDate)
{
throw new Exception("More than one item found in source collection with date " + desiredDate);
}
}
else
{
// if the current 'desired' item doesn't exist then
// create a dummy item using the provided factory
yield return defaultItemFactory(desiredDate);
}
}
}
The usage is as follows:
// first you must determine your desired list of dates which must be in order
// determine this however you want
var desiredDates = ....;
// fill in any holes
var ordersByDate = FillInEmptyDates(desiredDates,
// Source list (with holes)
orders,
// How do we get a date from an order
(order) => order.OrderDate,
// How do we create an 'empty' item
(date) => new
{
OrderDate = date,
OrderCount = 0,
Sales = 0
});
Must make sure there are no duplicates in the desired dates list
Both desiredDates and sourceData must be in order
Because the method is generic if you are using an anonymous type then the compiler will automatically tell you if your 'default' item is not the same 'shape' as a regular item.
Right now I include a check for duplicate items in sourceData but there is no such check in desiredDates
If you want to ensure the lists are ordered by date you will need to add extra code
Essentially what I ended up doing here is creating a list of the same type with all the dates in the range and 0 value for the count. Then union the results from my original query with this list. The major hurdle was simply creating a custom IEqualityComparer. For more details here: click here
You can generate the list of dates starting from "start" and ending at "finish", a then step by step check the number of count for each date separately

Categories