At least one object must implement IComparable. AsEnumerable() and Sum() - c#

I try to group multiple column on the tbl.AsEnumerable(),
I want to group cus, salesman, ppj and curr while the amt_base should be sum up.
Everything fine,
but when i use grp.Sum(r => r.Field<decimal>("amt_base")) on the sum of the amt it shown At least one object must implement IComparable. errors on the foreach ().
var results = from rows in tbl.AsEnumerable()
group rows by new
{
cus = rows["cus"],
salesman = rows["salesman"],
ppj = rows["ppj"],
curr = rows["curr"],
}into grp
orderby grp.Key
select new
{
cus = grp.Key.cus,
nm = grp.First()["nm"],
salesman = grp.Key.salesman,
ppj = grp.Key.ppj,
curr = grp.Key.curr,
amt_base = grp.Sum(r => r.Field<decimal>("amt_base")),
};
DataTable tbl2 = new DataTable();
tbl2.Columns.Add("cus");
tbl2.Columns.Add("nm");
tbl2.Columns.Add("salesman");
tbl2.Columns.Add("ppj");
tbl2.Columns.Add("curr");
tbl2.Columns.Add("amt_base");
decimal tamt_base = 0;
foreach (var item in results)
{
DataRow dr2 = tbl2.NewRow();
dr2["cus"] = item.cus;
dr2["nm"] = item.nm;
dr2["salesman"] = item.salesman;
dr2["ppj"] = item.ppj;
dr2["curr"] = item.curr;
dr2["amt_base"] = Math.Round(item.amt_base, 2, MidpointRounding.AwayFromZero);
tbl2.Rows.Add(dr2);
tamt_base += item.amt_base;
}

It can't determine how to order the rows based on an anonymous type comprised of of 4 random columns. It needs to be able to compare each instance to the previous, which is usually done by having your class implement the IComparable interface... but you can't with an anonymous type.
Remove this:
orderby grp.Key
If you really need some sort of ordering, try using an individual field:
orderby grp.Key.cus

Related

Unable to get a distinct list that contains summed values

I have posted this earlier but the objective of what I am trying to achieve seems to have lost hence re-posting it to get explain myself better.
I have a collection that has duplicate productnames with different values. My aim is to get a list that would sum these productnames so that the list contains single record of these duplicates.
For e.g
If the list contains
Product A 100
Product A 200
The result object should contain
Product A 300
So as you can see in my code below, I am passing IEnumerable allocationsGrouped to the method. I am grouping by productname and summing the Emv fields and then looping it so that I created a new list of the type List and pass it to the caller method. The problem what I seeing here is on the following line of code Items = group. Items now contains original list without the sum. Hence the inner foreach loop runs more than ones because there are duplicates which defeats my purpose. I finally need to return result object that has non duplicate values which are summed based on the above criteria. Could you please tell me where I am going wrong.
private static List<FirmWideAllocationsViewModel> CreateHierarchy(string manStratName, IEnumerable<FIRMWIDE_MANAGER_ALLOCATION> allocationsGrouped, List<FirmWideAllocationsViewModel> result)
{
var a = allocationsGrouped
.Where(product => !string.IsNullOrEmpty(product.PRODUCT_NAME))
.GroupBy(product => product.PRODUCT_NAME)
.Select(group => new
{
ProductName = group.Key, // this is the value you grouped on - the ProductName
EmvSum = group.Sum(x => x.EMV),
Items = group
});
var b = a;
var item = new FirmWideAllocationsViewModel();
item.Hierarchy = new List<string>();
item.Hierarchy.Add(manStratName);
result.Add(item);
foreach (var ac in b)
{
var productName = ac.ProductName;
var emvSum = ac.EmvSum;
foreach (var elem in ac.Items)
{
var item2 = new FirmWideAllocationsViewModel();
item2.Hierarchy = new List<string>();
item2.Hierarchy.Add(manStratName);
item2.Hierarchy.Add(elem.PRODUCT_NAME);
item2.FirmID = elem.FIRM_ID;
item2.FirmName = elem.FIRM_NAME;
item2.ManagerStrategyID = elem.MANAGER_STRATEGY_ID;
item2.ManagerStrategyName = elem.MANAGER_STRATEGY_NAME;
item2.ManagerAccountClassID = elem.MANAGER_ACCOUNTING_CLASS_ID;
item2.ManagerAccountingClassName = elem.MANAGER_ACCOUNTING_CLASS_NAME;
item2.ManagerFundID = elem.MANAGER_FUND_ID;
item2.ManagerFundName = elem.MANAGER_FUND_NAME;
item2.Nav = elem.NAV;
item2.EvalDate = elem.EVAL_DATE.HasValue ? elem.EVAL_DATE.Value.ToString("MMM dd, yyyy") : string.Empty;
item2.ProductID = elem.PRODUCT_ID;
item2.ProductName = elem.PRODUCT_NAME;
item2.UsdEmv = Math.Round((decimal)elem.UsdEmv);
item2.GroupPercent = elem.GroupPercent;
item2.WeightWithEq = elem.WEIGHT_WITH_EQ;
result.Add(item2);
}
}
return result;
}
change it to:
var result = allocationsGrouped
.Where(product => !string.IsNullOrEmpty(product.PRODUCT_NAME))
.GroupBy(product => product.PRODUCT_NAME)
.Select(group => {
var product = group.First();
return new FirmWideAllocationsViewModel()
{
Hierarchy = new List<string>() { manStratName, product.PRODUCT_NAME },
FirmID = product.FIRM_ID,
FirmName = product.Item.FIRM_NAME,
ManagerStrategyID = product.MANAGER_STRATEGY_ID,
ManagerStrategyName = product.MANAGER_STRATEGY_NAME,
ManagerAccountClassID = product.MANAGER_ACCOUNTING_CLASS_ID,
ManagerAccountingClassName = product.MANAGER_ACCOUNTING_CLASS_NAME,
ManagerFundID = product.MANAGER_FUND_ID,
ManagerFundName = product.MANAGER_FUND_NAME,
Nav = product.NAV,
EvalDate = product.EVAL_DATE.HasValue ? product.EVAL_DATE.Value.ToString("MMM dd, yyyy") : string.Empty,
ProductID = product.PRODUCT_ID,
ProductName = product.PRODUCT_NAME,
UsdEmv = Math.Round((decimal)product.UsdEmv),
GroupPercent = product.GroupPercent,
WeightWithEq = product.WEIGHT_WITH_EQ,
//assign aggregate Sum here
EmvSum = group.Sum(x => x.EMV),
};
});

using linq on datatable and putting result back into datatable with same format

What I m trying to do is relatively simple. I would like to use linq to compute some aggregated function on a group and then put the result back into a datatable of the same format. I did a lot of research and think I should use System.Data.DataSetExtensions and copy to datatable funtion. Here is my random datatable:
DataTable ADataTable = new DataTable("ADataTable");
// Fake table data
ADataTable.Columns.Add("PLANT", typeof(int));
ADataTable.Columns.Add("PDCATYPE_NAME", typeof(int));
ADataTable.Columns.Add("Month", typeof(int));
ADataTable.Columns.Add("Year", typeof(int));
ADataTable.Columns.Add("STATUS_NAME_REPORT", typeof(string));
ADataTable.Columns.Add("SAVINGS_PER_MONTH", typeof(double));
for (int i = 0; i < 15; i++)
{
for (int j = 1; j < 5; j++)
{
DataRow row = ADataTable.NewRow();
row["PLANT"] = j;
row["PDCATYPE_NAME"] = j;
row["Month"] = DateTime.Now.Month;
row["Year"] = DateTime.Now.Year;
row["STATUS_NAME_REPORT"] = "Report";
row["SAVINGS_PER_MONTH"] = j*i;
ADataTable.Rows.Add(row);
}
}
Now I will clone this format and do a simple sum on it via linq:
DataTable newtable = ADataTable.Clone();
// The actual query
IEnumerable<DataRow> query = (from rows in ADataTable.AsEnumerable()
group rows by new
{
PLANT = rows.Field<int>("PLANT"),
PDCATYPE_NAME = rows.Field<int>("PDCATYPE_NAME"),
Month = rows.Field<int>("Month"),
Year = rows.Field<int>("Year"),
STATUS_NAME_REPORT = rows.Field<string>("STATUS_NAME_REPORT")
} into g
select new
{
g.Key.PLANT,
g.Key.PDCATYPE_NAME,
g.Key.Month,
g.Key.Year,
g.Key.STATUS_NAME_REPORT,
sum = g.Sum(savings => savings.Field<double>("SAVINGS_PER_MONTH")),
});
newtable = query.CopyToDataTable<DataRow>();
The LINQ works fine but as soon as I put IEnumarable DataRow in front I get error that I cannot convert anonymys type to datarow. But if I put select new datarow I get an error that fields are unknown...
How do I proceed please?
You have multiple options, First is to use reflection to create a DataTable based on IEnumerable<T> and the other options is to populate your DataTable by enumerating your query like:
var query = ADataTable.AsEnumerable()
.GroupBy(row => new
{
PLANT = row.Field<int>("PLANT"),
PDCATYPE_NAME = row.Field<int>("PDCATYPE_NAME"),
Month = row.Field<int>("Month"),
Year = row.Field<int>("Year"),
STATUS_NAME_REPORT = row.Field<string>("STATUS_NAME_REPORT")
});
foreach (var g in query)
{
newtable.LoadDataRow(new object[]
{
g.Key.PLANT,
g.Key.PDCATYPE_NAME,
g.Key.Month,
g.Key.Year,
g.Key.STATUS_NAME_REPORT,
g.Sum(savings => savings.Field<double>("SAVINGS_PER_MONTH"))
}, LoadOption.OverwriteChanges);
}
The error in your code is because of selecting an anonymous type using select new and then trying to store it in IEnumerable<DataRow>. You can't specify DataRow in select as it is not accessible directly.
You may also see: How to: Implement CopyToDataTable Where the Generic Type T Is Not a DataRow
This also works:
newtable2 = ADataTable.AsEnumerable().GroupBy(a => new
{
PLANT = a.Field<int>("PLANT"),
PDCATYPE_NAME = a.Field<int>("PDCATYPE_NAME"),
Month = a.Field<int>("Month"),
Year = a.Field<int>("Year"),
STATUS_NAME_REPORT = a.Field<string>("STATUS_NAME_REPORT")
}).Select(g =>
{
var row = newtable2.NewRow();
row.ItemArray = new object[]
{
g.Key.PLANT,
g.Key.PDCATYPE_NAME,
g.Key.Month,
g.Key.Year,
g.Key.STATUS_NAME_REPORT,
g.Sum(r => r.Field<double>("SAVINGS_PER_MONTH"))
};
return row;
}).CopyToDataTable();
using System.Data.DataSetExtensions (Which requires a reference)

Filter List From List Using Linq

I have retrieved list of my specific class with 150 records.
Now, i want only those records which have Licenseid which are in my another int List.
For example My MainList
List<CustomerDocument> _list = GetListByID(CustomerID);
In this list i have column LicenseID,CustomerID,CustomerName,Type,Age e.t.c
And SecontList
List<int> type = new List<int>();
In Int list i add LicenseID one by one dynamically.
Public class CustomerDocument
{
public int LicenseID{get;set;};
public int CustomerID{get;set;};
public string CustomerName{get;set;};
public int Age{get;set;};
}
This is my CustomerDocument class for which i am getting list.
And now suppose, If Int list has three records , then i want those records from my Main List which have these three LicenseID in my Int List using Linq.
_list = ???
List<CustomerDocument> list = new List<CustomerDocument>();
List<Int> types = new List<Int>();
MapSearchSalesEntities datacontext = new MapSearchSalesEntities();
var collection = ddlLicenseType.CheckedItems;
if (collection.Count > 0)
{
foreach (var item in collection)
{
int value = Convert.ToInt32(item.Value);
types .Add(value);
}
}
var query = (from t1 in datacontext.Licenses
select new CustomerDocument
{
LicenseID = t1.LicenseID,
CustomerID = t1.CustomerID,
CustomerName= t1.CustomerName,
Age= t1.Age,
});
list = query.ToList(); ---gives 150 Records
if (types != null && types.Count > 0)
{
list = list.Where(c => types.Contains(c.LicenseID)).ToList(); --- Gives 0 Records
}
The most efficient approach is to use Enumerable.Join:
var documents = from doc in _list
join licenseID in type
on doc.LicenseID equals licenseID
select doc;
if you want to replace the list:
_list = documents.ToList();
You could also use Enumerable.Where + List.Contains which is not as efficient but shorter:
_list = _list.Where(d => type.Contains(d.LicenseID)).ToList();
Using the LinQ Where method, this is very easy:
_list = _list.Where(c => type.Contains(c.LicenseID)).ToList();
Here is a linq query
var result = (from cust in _list join id in type on cust.LicenseID equals id select cust).ToArray();

SUM for all columns in LINQ

I have an SQL table with many columns(~200).
I want to create a LINQ query to obtain the sum of all rows by column. The result to be one row which represents the SUM of each column.
How can be done this LINQ query?
It's difficult to create a specific .Sum(...) for each column.
double sum = Table.Select(t => t.Amount ?? 0).Sum();
OR
double sum = Table.Sum(t => t.Amount ?? 0);
You could try using:
var results = (from i in yourCollection
group g by i.Column into g
select new
{
ColumnName = i.Column,
ColumnTotal = i.Sum(x => x.Value)
}).ToList();
Use the right tool for the job. This time it isn't Entity Framework. Here is a simple ADO.NET routine:
public double[] SumAllColumns(IDbConnection connection)
{
using (var cmd = connection.CreateCommand())
{
cmd.CommandText = "SELECT * FROM YourTable";
using (var reader = cmd.ExecuteReader())
{
var values = new double[reader.FieldCount];
while (reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
values[i] += reader.GetDouble(i);
}
}
return values;
}
}
}
The method returns an array with the sum of each column.
var sums = (from c in columns
group c by c.columnName into g
select new
{
SumCol1 = g.Sum(x => x.Col1),
SumCol2 = g.Sum(x => x.Col2)
}).SingleOrDefault;
To access the variables in a console application for example...
Console.Write(sums.SumCol1.ToString() + " : " + sums.SumCol2.ToString());
I've used this solution:
from res in datacTx.Table
where ...
group re by re.id into g
select new
{
col1 = g.Sum(x => x.col1),
col2 = g.Sum(x => x.col2),
...
};
but I can't access now any of the values or I can't convert the result into a list in which each column represent an entry in the list.
Until now I've used this, to access the values of linq query:
foreach (var propertyInfo in res.GetType().GetProperties())
{
...
}
but now this is not working because I don't have any properties.
You could use a sqlcommand, which execution returns for each row a result array, then use array.Sum().
Or add a sql computed column.

How to concat only unique columns with data

I am using this below:
public static DataTable DataTableJoiner(DataTable dt1, DataTable dt2)
{
using (DataTable targetTable = dt1.Clone())
{
var dt2Query = dt2.Columns.OfType<DataColumn>().Select(dc =>
new DataColumn(dc.ColumnName, dc.DataType, dc.Expression,
dc.ColumnMapping));
var dt2FilterQuery = from dc in dt2Query.AsEnumerable()
where targetTable.Columns
.Contains(dc.ColumnName) == false
select dc;
targetTable.Columns.AddRange(dt2FilterQuery.ToArray());
var rowData = from row1 in dt1.AsEnumerable()
join row2 in dt2.AsEnumerable()
on row1.Field<int>("Code") equals
row2.Field<int>("Code")
select row1.ItemArray
.Concat(row2.ItemArray
.Where(r2 =>
row1.ItemArray.Contains(r2) == false)).ToArray();
foreach (object[] values in rowData) targetTable.Rows.Add(values);
return targetTable;
}
}
There is a problem with this line:
select row1.ItemArray.Concat(row2.ItemArray.Where(r2 =>
row1.ItemArray.Contains(r2) == false)).ToArray();
It seems to be saying don't include me if this value (rather than column) already exists.
I am using this method to join two tables together based on a column that both tables share, but I only want the unique columns with data of both tables as a final result.
Any ideas?
I am not sure if I understand your requirement 100%, but this:
row2.ItemArray.Where(r2 => row1.ItemArray.Contains(r2) == false)
will filter out those items that happen to appear in any column of table 1, not just the column you are joining on.
So what I would try to do is filter the item based on the index, using an overload of the Where extension method:
// Get the index of the column we are joining on:
int joinColumnIndex = dt2.Columns.IndexOf("Code");
// Now we can filter out the proper item in the rowData query:
row2.ItemArray.Where((r2,idx) => idx != joinColumnIndex)
...
No, wait. Here:
var dt2FilterQuery = from dc in dt2Query.AsEnumerable()
where targetTable.Columns
.Contains(dc.ColumnName) == false
select dc;
You are filtering out all columns of table 2 whose name also appear in table 1. So what you probably want is this:
public static DataTable DataTableJoiner(DataTable dt1, DataTable dt2)
{
DataTable targetTable = dt1.Clone();
var dt2Query = dt2.Columns.OfType<DataColumn>().Select(dc =>
new DataColumn(dc.ColumnName, dc.DataType, dc.Expression,
dc.ColumnMapping));
var dt2FilterQuery = from dc in dt2Query.AsEnumerable()
where !targetTable.Columns.Contains(dc.ColumnName)
select dc;
var columnsToAdd = dt2FilterQuery.ToArray();
var columnsIndices = columnsToAdd.Select(dc => dt2.Columns.IndexOf(dc.ColumnName));
targetTable.Columns.AddRange(columnsToAdd);
var rowData = from row1 in dt1.AsEnumerable()
join row2 in dt2.AsEnumerable()
on row1.Field<int>("Code") equals
row2.Field<int>("Code")
select row1.ItemArray
.Concat(row2.ItemArray
.Where((r2,idx) =>
columnsIndices.Contains(idx))).ToArray();
foreach (object[] values in rowData) targetTable.Rows.Add(values);
return targetTable;
}
Btw. I don't quite understand why you are wrapping the DataTable you return in a using statement. Imho it is kind of pointless to dispose the very object you return to your caller right away...

Categories