Select All distinct values in a column using LINQ - c#

I created a Web Api in VS 2012.
I am trying to get all the value from one column "Category", that is all the unique value, I don't want the list to be returned with duplicates.
I used this code to get products in a particular category. How do I get a full list of categories (All the unique values in the Category Column)?
public IEnumerable<Product> GetProductsByCategory(string category)
{
return repository.GetAllProducts().Where(
p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));
}

To have unique Categories:
var uniqueCategories = repository.GetAllProducts()
.Select(p => p.Category)
.Distinct();

var uniq = allvalues.GroupBy(x => x.Id).Select(y=>y.First()).Distinct();
Easy and simple

I have to find distinct rows with the following details
class : Scountry
columns: countryID, countryName,isactive
There is no primary key in this. I have succeeded with the followin queries
public DbSet<SCountry> country { get; set; }
public List<SCountry> DoDistinct()
{
var query = (from m in country group m by new { m.CountryID, m.CountryName, m.isactive } into mygroup select mygroup.FirstOrDefault()).Distinct();
var Countries = query.ToList().Select(m => new SCountry { CountryID = m.CountryID, CountryName = m.CountryName, isactive = m.isactive }).ToList();
return Countries;
}

Interestingly enough I tried both of these in LinqPad and the variant using group from Dmitry Gribkov by appears to be quicker. (also the final distinct is not required as the result is already distinct.
My (somewhat simple) code was:
public class Pair
{
public int id {get;set;}
public string Arb {get;set;}
}
void Main()
{
var theList = new List<Pair>();
var randomiser = new Random();
for (int count = 1; count < 10000; count++)
{
theList.Add(new Pair
{
id = randomiser.Next(1, 50),
Arb = "not used"
});
}
var timer = new Stopwatch();
timer.Start();
var distinct = theList.GroupBy(c => c.id).Select(p => p.First().id);
timer.Stop();
Debug.WriteLine(timer.Elapsed);
timer.Start();
var otherDistinct = theList.Select(p => p.id).Distinct();
timer.Stop();
Debug.WriteLine(timer.Elapsed);
}

Related

Linq query Group By with contains

I have a list of the List of string that is Currency Code.
var currencyCode = new List<string>() { "USD", "SGD", "KWD", "BHD", "LYD" };
And i Have another complex object.
var rate = new List<Rate>()
{
new Rate() { CurrencyName = "USD (SMALL)",CurrencyCode = "USD SMALL",BranchName="Branch1"},
new Rate() { CurrencyName = "SGD BIG",CurrencyCode = "SGD BIG",BranchName="Branch1"},
new Rate() { CurrencyName = "KUWAIT DINAR",CurrencyCode = "KWD",BranchName="Branch1"},
new Rate() { CurrencyName = "USD BIG (100,50)",CurrencyCode = "USD BIG",BranchName="Branch1"},
new Rate() { CurrencyName = "USD MEDIUM (10,20)",CurrencyCode = "USD MEDIUM",BranchName="Branch1"},
};
I will have the matched currency in the below list:
var matchedCurrency = from c in rate
where currency.Any(w => c.CurrencyCode.Contains(w))
select c;
What i wanted is that the matching currency list should be in grouped, grouped by currency code.
I tried by the following way but it did not worked.
var Grp = rate.GroupBy(item => currency.Any(w => item.CurrencyCode.Contains(w)))
.Select(group => new
{
group.Key,
DataList = group.ToList()
});
I don't get i am actually missing. I have tried by various ways.
I know i can loop through the rate and push into another object. But that does not look nice i wanted to do this by using Linq. But i could not achieve the point.
Output will be displayed with this object:
public class CurrencyMap
{
public string Code { get; set; }
public List<Currency> currency { get; set; }
}
public class Currency
{
public string CurrencyName { get; set; }
public string CurrencyCode { get; set; }
public string BranchName { get; set; }
}
enter code here
EDIT:
I missed the things at first but i also need to have the empty list if the matching code was not found in the rate.
In Rate there is not the matching list for "BHD", "LYD". But i also need to have the empty list with the code "BHD", "LYD"
First select the matching currency code, then group by the selected code.
var groupedRates = rate
.Select(r => new
{
rate = r,
code = currencyCode.FirstOrDefault(c => r.CurrencyCode.Contains(c))
})
.GroupBy(x => x.code, x => x.rate); //maybe you don't want to throw away the resolved code like I do in the element selector...
Edit: I guess I was a bit to focused on the grouping aspect. Since you want to include all currency codes and mentioned a specific output structure, forget about grouping and just select your result:
var groupedRatesList = currencyCode
.Select(c => new CurrencyMap
{
Code = c,
currency = rate
.Where(x => x.CurrencyCode.Contains(c))
.Select(x => new Currency
{
BranchName = x.BranchName,
CurrencyCode = x.CurrencyCode, // or maybe you want to insert c here?
CurrencyName = x.CurrencyName
})
.ToList()
})
.ToList();
It is a rather hacky approach but you could use Regex.Match to achieve this. The basic idea is that you need the value from currencyCode as the key for your grouping.
This can be returned by a sucessfull match with regex. The property Match.Value will contain the string value for the key
Disclaimer: Unfortunately all negative matches will be return also as empty groups. You would need to filter then the empty groups out:
var result = currencyCode.SelectMany
(
x=> rate.Where(r=> r.CurrencyCode.Contains(x))
.GroupBy(r=> Regex.Match(r.CurrencyCode, x).Value)
).Where(x=> !string.IsNullOrWhiteSpace(x.Key));
Actually it works also without regex:
var result = rate.GroupBy(r => currencyCode.FirstOrDefault(c=> r.CurrencyCode.Contains(c)))
.Where(x=> !string.IsNullOrWhiteSpace(x.Key));
Disclaimer 2: Like all pattern matching it will lead to problems if you have ambiguous patterns. If a CurrencyCode value contains more than 1 of the abriviations ( may be inside the word ) you can get non sensical results/ or double entries.
Although I found it to be an intriguing problem to solve with linq, personally I would refrain from this approach. If I would have to return to this code after 9 months to maintain it, I would be way more happy to read this:
Dictionary<string,IEnumerable<Rate>> groupedSet = new Dictionary<string, IEnumerable<Rate>>();
foreach (var key in currencyCode)
{
IEnumerable<Rate> _result = rate.Where(x => x.CurrencyCode.Contains(key));
if (_result.Any())
{
groupedSet.Add(key, _result);
}
}
than to start remembering what da hack I wrote back then and what I might have thought of when I wrote it back then....
Not the best way since this approach assumes you have fixed length of Currency but you can try this:-
int currencyLength = currencyCode.First().Length;
var result = rate.Where(x => currencyCode.Any(z => x.CurrencyCode.Contains(z)))
.GroupBy(x => x.CurrencyCode.Substring(0, currencyLength))
.Select(x => new
{
Currency = x.Key,
List = x.ToList()
});
Fiddle
Try this;
var currencyCodes = new List<string>() { "USD", "SGD", "KWD", "BHD", "LYD" };
var currencies = new List<Currency>()
{
new Currency() { CurrencyName = "USD (SMALL)",CurrencyCode = "USD SMALL",BranchName="Branch1"},
new Currency() { CurrencyName = "SGD BIG",CurrencyCode = "SGD BIG",BranchName="Branch1"},
new Currency() { CurrencyName = "KUWAIT DINAR",CurrencyCode = "KWD",BranchName="Branch1"},
new Currency() { CurrencyName = "USD BIG (100,50)",CurrencyCode = "USD BIG",BranchName="Branch1"},
new Currency() { CurrencyName = "USD MEDIUM (10,20)",CurrencyCode = "USD MEDIUM",BranchName="Branch1"},
};
List<CurrencyMap> maps = currencies.Select(c => new
{
Currency = c,
FoundCode = currencyCodes.FirstOrDefault(code => c.CurrencyCode.Contains(code))
})
.Where(o => o.FoundCode != null)
.GroupBy(o => o.FoundCode)
.Select(grp => new CurrencyMap() { Code = grp.Key, Currencies = grp.Select(o => o.Currency).ToList() })
.ToList();

How to group and merge/flatten a list of anonymous objects in LINQ

I have a list of anonymous objects generated by a LINQ query that I do not have access to modify.
The objects have the following properties:
OrderId, RepId, FirstName, LastName, Address
Each "Rep" often places multiple orders, so there are a lot of rows where the only difference is the OrderId. There is a requirement that if the same Rep has placed multiple orders, to batch these together in groups of 6 with a new structure:
OrderId1, OrderId2, ..., OrderId6, RepId, FirstName, LastName, Address
But if the rep has placed say 8 orders, there would be a batch of 6 and a batch of 2. So the new objects don't always have the same number of properties.
I've started by grouping the initial result set by RepId, but I have no clue where to go next.
Is this possible using LINQ?
As your output have anonymous objects with different schema, that make the thing a little more complicate.
Ideally you should design your entity class to use list for orders instead of property like "OrderId1", "OrderId2"... That is not extensible and error prone. But for that specific question, we can combine LINQ and ExpandoObject to achieve this.
orders.GroupBy(order => order.RepId)
.SelectMany(orderGroup => orderGroup.Select((order, i) => new {
Order = order,
ReqId = orderGroup.Key,
SubGroupId = i / 6
}))
.GroupBy(h => new {
ReqId = h.ReqId,
SubGroupId = h.SubGroupId,
FirstName = h.Order.FirstName,
LastName = h.Order.LastName,
Address = h.Order.Address
})
.Select(orderWithRichInfo => {
dynamic dynamicObject = new ExpandoObject();
int i = 1;
foreach(var o in orderWithRichInfo)
{
((IDictionary<string, object>)dynamicObject).Add("OrderId" + i, o.Order.OrderId);
i++;
}
((IDictionary<string, object>)dynamicObject).Add("FirstName", orderWithRichInfo.Key.FirstName);
((IDictionary<string, object>)dynamicObject).Add("LastName", orderWithRichInfo.Key.LastName);
((IDictionary<string, object>)dynamicObject).Add("Address", orderWithRichInfo.Key.Address);
return dynamicObject;
});
Hope it helps.
First option.
If you want to get 6 OrderId-s as a list, you can create
class OrderBundle
{
public int RepId { get; set; }
public List<int> OrderIds { get; set; }
}
Group your items:
var orderBundels = orderList
.GroupBy(m => m.RepId)
.Select(g => new OrderBundle
{
RepId = g.Key,
OrderIds = g.Select(m => m.OrderId).ToList()
});
And then split them into groups:
List<OrderBundle> dividedOrderBundels = new List<OrderBundle>();
foreach (OrderBundle orderBundle in orderBundels)
{
int bundelCount = (int)Math.Ceiling(orderBundle.OrderIds.Count() / 6.0);
for (int i = 0; i < bundelCount; i++)
{
OrderBundle divided = new OrderBundle
{
RepId = orderBundle.RepId,
OrderIds = orderBundle.OrderIds.Skip(i * 6).Take(6).ToList()
};
dividedOrderBundels.Add(divided);
}
}
Second option:
You can achieve the same result without creating model like below:
var result = orderList
.GroupBy(m => m.RepId)
.SelectMany(g => g.Select((m, i) => new
{
RepId = g.Key,
FirstName = m.FirstName,
LastName = m.LastName,
Address = m.Address,
OrderId = m.OrderId,
BunleIndex = i / 6
}))
.GroupBy(m => m.BunleIndex)
.Select(g => new
{
RepId = g.Select(m => m.RepId).First(),
FirstName = g.Select(m => m.FirstName).First(),
LastName = g.Select(m => m.LastName).First(),
Address = g.Select(m => m.Address).First(),
OrderIds = g.Select(m => m.OrderId).ToList()
})
.ToList()

How can I return a Dictionary<string,Dictionary<int,decimal>> from this method?

I have a LINQ statement which returns an IQueryable of the class below.
Class:
public class SupplierSummaryReport {
public int Year { get; set; }
public string SupplierName { get; set; }
public decimal TurnOverValues { get; set; }
}
Eg: {2012,Supplier1,90},{2011,Supplier2,95}
However, I then need to convert this data into a Dictionary of Dictionaries. I have the extension method which me and a friend have built, however we are stumped at the final section.
Extension Method:
public static Dictionary<TFirstKey, Dictionary<TSecondKey, TValue>> Pivot<TSource, TFirstKey, TSecondKey, TValue>(this IEnumerable<TSource> source, Func<TSource, TFirstKey> firstKeySelector, Func<TSource, TSecondKey> secondKeySelector, Func<TSource, TValue> Value) {
var retVal = new Dictionary<TFirstKey, Dictionary<TSecondKey, TValue>>();
var l = source.ToLookup(firstKeySelector);
foreach (var item in l) {
var dict = new Dictionary<TSecondKey, TValue>();
retVal.Add(item.Key, dict);
var subdict = item.ToLookup(secondKeySelector);
foreach (var subitem in subdict) {
dict.Add(subitem.Key, subitem.Value /*Insert value here*/);
}
}
return retVal;
}
We call the method like so:
public Dictionary<string,Dictionary<int,decimal>> GetSupplierSummaryReportData(List<int> last5Years) {
_ctx.Database.CommandTimeout = 5 * 60;
//values - Gets the data required for the Supplier summary report in a hierachical order. E.g - {2012,Supplier1,3},{2011,Supplier1,4}
var values = (from i in _ctx.Invoices
where i.Turnover == true
group i by new { i.AccountName, i.InvoiceDate.Year } into summ
select new APData.Audit.Models.ReportModels.SupplierSummaryReport {
Year = summ.Key.Year,
SupplierName = summ.Key.AccountName,
TurnOverValues = summ.Sum(r => r.VATAmount_Home ?? 0)
});
//accounts - Get all the account names
var accounts = (from i in _ctx.Invoices
where i.Turnover == true
group i by new { i.AccountName } into summ
select new {
summ.Key.AccountName
});
/*crossJoin - select all SupplierNames from accounts and all years from last5Years and assign each SupplierName the last 5 years. Assign each turnover value null for each year.
This is in preparation for the cross join as not all suppliers will have the last 5 year in data */
var crossJoin = accounts.SelectMany(a => last5Years, (a, y) => new APData.Audit.Models.ReportModels.SupplierSummaryReport {
Year = y,
SupplierName = a.AccountName,
TurnOverValues = 0
});
/*Join crossJoin and values together, wherever the join is empty, assign the cross join values. If not assign the turnover value from a*/
var result =
(from cj in crossJoin
join v in values
on new { cj.Year, cj.SupplierName }
equals new { v.Year, v.SupplierName } into lJoin
from a in lJoin.DefaultIfEmpty(new APData.Audit.Models.ReportModels.SupplierSummaryReport {
Year = cj.Year,
SupplierName = cj.SupplierName,
TurnOverValues = cj.TurnOverValues
})
select new APData.Audit.Models.ReportModels.SupplierSummaryReport {
Year = cj.Year,
SupplierName = cj.SupplierName,
TurnOverValues = a.TurnOverValues
}).OrderBy(r => r.SupplierName).ThenBy(r => r.Year);
return result.Pivot(r => r.SupplierName, c => c.Year, y => y.TurnOverValues);
We need to insert the decimal value of TurnOverValues as the third item in the dictionary.
Expected outcome:
{Supplier1, {2012,60}}, {Supplier2, {2014,90}}
If anyone needs anymore information, please let me know.
TL;DR, but if what you have is an IEnumerable<SupplierSummaryReport> and you're sure that each SupplierName, Year, TurnOver combination is unique or can be made unique then ToDictionary makes this easy:
var data = new List<SupplierSummaryReport> { ... };
// avoiding var just to verify correct result Type
Dictionary<string, Dictionary<int, decimal>> result = data
.GroupBy(ssr => ssr.SupplierName)
.ToDictionary(g1 => g1.Key, g1 => (
g1.GroupBy(g2 => g2.Year)
.ToDictionary(g3 => g3.Key,
g3 => g3.First().TurnOverValues))
);
Maybe the g3.First() should become .Single() or .Sum() or something.

Count and Groupby using linq from sql query

Im trying to create a table that counts all orders and groups them in a table from sql to linq to use in a bar graph with google charts.
Table`
Orders Status
8 Created
3 Delayed
4 Enroute
sql
SELECT Count (OrderID) as 'Orders', order_status FROM [ORDER]
where order_status ='Created'OR order_status= 'Delayed' OR order_status='Enroute'
group by order_status
controller
public ActionResult GetChart()
{
var Orders = db.Order.Select(a => new { a.OrderID, a.order_status })
.GroupBy(a => a.order_status);
return Json(Orders, JsonRequestBehavior.AllowGet);
}
this is not displaying the correct results as the linq seems to be wrong.
can someone please point me in the right direction? I am relatively new to this.
thanks in advance.
This should work:-
var result = db.Order.Where(x => x.order_status == "Created"
|| x.order_status == "Delayed"
|| x.order_status == "Enroute")
.GroupBy(x => x.order_status)
.Select(x => new
{
order_status = x.Key,
Orders = x.Count()
});
Or if you prefer query syntax then:-
var result = from o in db.Order
where o.order_status == "Created" || o.order_status == "Delayed"
|| o.order_status == "Enroute"
group o by o.order_status
select new
{
orderStatus = x.Key,
Counts = x.Count()
};
I think you want to group by Status and count total number of orders in each group (I build a simple console program to demonstrate). I suppose the data is:
Orders Status
8 Created
3 Delayed
4 Enroute
2 Created
1 Delayed
Order.cs
public class Order
{
public Order(int orderId, string status)
{
OrderId = orderId;
Status = status;
}
public int OrderId { get; set; }
public string Status { get; set; }
}
Program.cs
class Program
{
static void Main(string[] args)
{
// Data
var orders = new List<Order>
{
new Order(8, "Created"),
new Order(3, "Delayed"),
new Order(4, "Enroute"),
new Order(2, "Created"),
new Order(1, "Delayed"),
};
// Query
var query = orders
.GroupBy(x => x.Status)
.Select(x => new {Status = x.Key, Total = x.Count()});
// Display
foreach (var item in query)
{
Console.WriteLine(item.Status + ": " + item.Total);
}
Console.ReadLine();
}
}
The one you need to focus in is query. After using GroupBy, you will have a list of groups. For each group, the Key is the criteria to group (here is the Status). Then, we call Count() to get the total number of element in that group.
So, from the program above, the output should be:
Created: 2
Delayed: 2
Enroute: 1

Cannot convert List<model> to model

Having some issues with my code, its a many to many between 3 tables, im using LINQ to entities, first I group the quote ids then use the id to get the info from each table and try to put it into a viewmodel, im not sure my inhertance is right
There should be one quote with multiple prices from multiple suppliers for multiple items, the item names need to be in the column and the prices below.
Anyway the issue below is
Error 3 The best overloaded method match for
System.Collections.Generic.List<ITAPP.Models.tblQuotes>.Add(ITAPP.Models.tblQuotes)
has some invalid arguments PurchasingController.cs 48 17 ITAPP`
Error 4 Argument 1: cannot convert from
System.Collections.Generic.List<ITAPP.Models.tblQuotes>' to 'ITAPP.Models.tblQuotes'
PurchasingController.cs 48 37 ITAPP`
and here is the code
var tblQuoting =
from d in db.Quotes_Items_Suppliers
group d by new
{
d.QuoteID
} into g
select new {
QuoteID = g.Key
};
var model = new List<QuoteViewModel>();
foreach (var Quotes in tblQuoting)
{
var ModelItem = new QuoteViewModel();
ModelItem.Quote = new List<tblQuotes>();
ModelItem.Suppliers = new List<tblSuppliers>();
ModelItem.Items = new List<tblItems>();
ModelItem.Prices = new List<tblQuotes_Items_Suppliers>();
//Add the quote info to model
int QuoteID = Convert.ToInt32(Quotes.QuoteID);
var Quote = (from d in db.Quotes
where d.ID == QuoteID
select d).ToList();
ModelItem.Quote.Add(Quote);
//add all the suppliers to the quote model
var Suppliers = (from d in db.Quotes_Items_Suppliers.Include(t => t.tblSuppliers)
where d.QuoteID == QuoteID
select d).ToList();
ModelItem.Suppliers.Add(Suppliers);
//add the items to the quote model
var Items = (from d in db.Quotes_Items_Suppliers.Include(t => t.tblItems)
where d.QuoteID == QuoteID
select d).ToList();
ModelItem.Items.Add(Items);
model.Add(ModelItem);
}
return View("Index", model);
this is my model (if its right?)
public class QuoteViewModel
{
public List<tblQuotes> Quote { get; set; }
public List<tblSuppliers> Suppliers { get; set; }
public List<tblItems> Items { get; set; }
public List<tblQuotes_Items_Suppliers> Prices { get; set; }
}
index
Use AddRange to add sequence of items to list:
ModelItem.Quote.AddRange(Quote);
ModelItem.Suppliers.AddRange(Suppliers);
ModelItem.Items.AddRange(Items);
Or simply assign lists without initialization (thus you will avoid creating intermediate list and copying items from one list to another):
ModelItem.Quote = Quote;
ModelItem.Suppliers = Supplies;
ModelItem.Items = Items;
Or even use object initalizer:
var ModelItem = new QuoteViewModel {
Quote = db.Quotes.Where(q => q.ID == QuoteID).ToList(),
Suppliers = db.Quotes_Items_Suppliers.Include(t => t.tblSuppliers)
.Where(s => s.QuoteID == QuoteID).ToList(),
Items = db.Quotes_Items_Suppliers.Include(t => t.tblItems)
.Where(i => i.QuoteID == QuoteID).ToList()
};

Categories

Resources