Convert IEnumerable to Sum item Linq - c#

I need to convert an IEnumerable to something else to be able to use Sum. What can I do to achieve this?
CsTotCommit = h.Sum(x => x.CSTotCommit)
I receive an error stating that:
CapStackTrancheDTO does not contain a definition for 'Sum' accepting a first argument of type CapStackTrancheDTO.
IEnumerable<CapStackTrancheDTO> debtRank = debt.AsEnumerable()
.Select((g,index) =>
new CapStackTrancheDTO
{
Rankid = index + 1,
CsTrancheId = g.CsTrancheId,
CsId = g.CsId,
CsTotCommit = g.CsTotCommit,
});
IEnumerable<CapStackTrancheDTO> debtSum = debtRank
.Select(h =>
new
{
CsId = h.CsId,
CsTotCommit = h.Sum(x => x.CSTotCommit)
});
Here are the class defintions:
public class CapStackTrancheDTO
{
public int? Rankid { get; set; }
public int? CsTrancheId { get; set; }
public int? CsId { get; set; }
public decimal? CsTotCommit { get; set; }
}
There are multiple records which I want to group by CsId and SUM.

From the comments, you've said that you want to group by CsId and then sum.
Currently, you're not applying any grouping.
Use the .GroupBy method, like this:
IEnumerable<CapStackTrancheDTO> debtSum = debtRank
.GroupBy(h => h.CsId)
.Select(h =>
new CapStackTrancheDTO
{
CsId = h.Key, // the value we grouped on above is now available in `Key`
CsTotCommit = h.Sum(x => x.CSTotCommit)
}
);

Related

Error when using Select() instead of Include() in a query

I have the following query:
var catInclude = _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Include(x => x.CatItems)
.SingleOrDefault(p => p.Id == request.ProvId
cancellationToken: cancellationToken);
As I don't want to get all properties from CatItems with Include(), I have created the following query:
var catSelect = _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Select(p ==> new
{ Provider = p,
Items = p.CatItems.Select(x => new List<CatItems> { new CatItems
{ Id = x.Id, Name = x.Name, Price = x.Price } }
})})
SingleOrDefault(cancellationToken: cancellationToken);
But something is wrong in the 2nd query because here return _mapper.ProjectTo<CatDto>(cat) I get the following error:
Argument 1: cannot convert from '<anonymous type: Db.Entities.Cat Prov, System.Colletions.Generic.IEnumerable<System.Colletions.Generic.List<Models.CatItems> > Items>' to 'System.Linq.IQueryable'
Here is my CatDto:
public class CatDto
{
public int ProvId { get; set; }
public List<CatItems> CatItems { get; set; }
}
Here are my entities:
public class Prov
{
public int Id { get; set; }
public Cat Cat { get; set; }
}
public class Cat
{
public int Id { get; set; }
public int ProvId { get; set; }
public List<CatItems> CatItems { get; set; }
}
public class CatItems
{
public int Id { get; set; }
public int CatId { get; set; }
public DateTime CreatedOn { get; set; }
}
Is there a way to recreate the 2nd query and use it?
Main difference that instead of returning List of CatItems, your code returns IEnumerable<List<CatItems>> for property Items.
So, just correct your query to project to List:
var catSelect = await _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Select(p => new CatDto
{
ProvId = p.ProvId,
Items = p.CatItems.Select(x => new CatItems
{
Id = x.Id,
Name = x.Name,
Price = x.Price
})
.ToList()
})
.SingleOrDefaultAsync(cancellationToken: cancellationToken);
I mean, even the exception is pretty self-explanatory. Nevertheless:
You are performing a .Select(...). It returns an Anonymous type. So, your catSelect is an anonymous type, thus the AutoMapper fails.
The quickest fix is to just cast (Cat)catSelect before mapping.
Or, you can dig deeper into how does AutoMapper play with anonymous types.
I feel like you can make most of the classes inherent Id and why is public cat CAT {get; set;} i thought you were supposed to initialize some kind of value

Groupby followed by orderby keeping the group intact and then sort individual group

This one seemed a little tough for me to do using LINQ. I have a list of alerts and an alert looks like:
public short AlertTypeId { get; set; }
public string Type { get; set; }
public int Severity { get; set; }
public bool IsOverview { get; set; }
public string Title { get; set; }
public string Text { get; set; }
public int Position { get; set; }
public string Id { get; internal set; }
I want to first group these alerts by alertIDType,
sort elements in an individual group by severity in desc and ...
Then sort entire groups by the same alertIDType in a specific order (I have a utils sorter for that mentioned below)
The sorting code currently looks like:
var alerts = new BPAlerts
{
AllAlerts = intakeAlerts.Select(
alert => new BPAlert
{
AlertTypeId = alert.AlertTypeId ?? 8100,
IsOverview = alert.IsOverviewAlert.GetValueOrDefault(),
Text = alert.AlertText,
Title = alert.AlertTitle,
Type = alert.AlertTypeId == 8106 ? "warning" : "report",
Severity = alert.AlertSeverity.GetValueOrDefault(),
Position = alert.Position.GetValueOrDefault()
}).ToList()
};
alerts.AllAlerts = alerts.AllAlerts.GroupBy(a => a.AlertTypeId)
.OrderByDescending(g => Utils.AlertSorterUtil.SortByAlertTypeId(g.Key))
.SelectMany(g => g.OrderByDescending(a => a.Severity)).ToList();
// Alerts displayed on the overview page
alerts.OverviewAlerts =
alerts.AllAlerts
.ToList()
.Where(a => a.IsOverview && !string.IsNullOrEmpty(a.Title))
.Take(3)
.ToList();
Utils sorter method looks like:
public class AlertSorterUtil
{
public static int SortByAlertTypeId(short alertTypeId)
{
var orderArray = new int[]
{
8106, // Confirmed Out of Business
8105, // Bankruptcy
8111, // Lack of Licensing
8109, // Investigations
8103, // Government Actions
8104, // Pattern of Complaints
8112, // Customer Reviews
8110, // Accreditation
8101, // Misuse of BBB Name
8107, // Advisory
8102, // Advertising Review
};
for (int orderValue = 0; orderValue < orderArray.Length; orderValue++)
{
if (alertTypeId == orderArray[orderValue])
{
return orderValue;
}
}
return int.MaxValue;
}
}
Assuming you have a list of alerts to perform all these operations on. do the following with LINQ. It will group alerts by AlertTypeId then sort the groups by AlertTypeId and finally sort individual groups by Severity asc.
var groupedAlerts = alerts.GroupBy(a => a.AlertTypeId).OrderBy(g => Utils.AlertSorterUtil.SortByAlertTypeId(g.Key)).Select(g => g.OrderByDescending(a => a.Severity).ToList()).ToList();

How to write dynamic select expression

I need to write some dynamic select expression on entity framework something like in the example.
var list = db.Article
.GroupBy(x => x.CategoryId)
.Select(x => new ArtDto
{
No = x.Select(c => c.NUMBER).FirstOrDefault(),
UserName = x.Key,
Count = x.Count()
})
.ToList();
I can write group by with expression like this:
Expression<Func<Article, int>> groupByExp;
groupByExp = (x) => x.CategoryId;
So I can replace actual expression with groupByExp.
var list = db.Article
.GroupBy(groupByExp)
.Select(x => new ArtDto
{
No = x.Select(c => c.NUMBER).FirstOrDefault(),
UserName = x.Key,
Count = x.Count()
})
.ToList();
I also want to write another expression for select. So I can send it to another function and it will be dynamic on that function.
Expression<Func<Article, bool>> selectExp;
selectExp = (x) => new ArtDto { ... };
Is it possible? Do you have any idea or tutorial for that?
Yes it is possible,
before start you need to:
Create the new object for selected properties
Map your model to the new object
lets consider that you have your model Article and you need to return the new model ArticleSummary as below
public class Article {
public int id { get; set; }
public string Title { get; set; }
public string Introduction { get; set; }
public string AuthorId { get; set; }
public AppUser Author { get; set; }
public DateTime PublishDate { get; set; }
}
public class ArticleSummary {
public int Id { get; set; }
public string Title { get; set; }
public string Introduction { get; set; }
}
and here is the mapping :
Expression<Func<Article, ArticleSummary>> mapArticle = x => new ArticleSummary {
Id = x.Id,
Title = x.Title,
Introduction = x.Introduction
};
and here is the "simplified" data function :
// T is Article model
// U is ArticleSummary model
public async Task<ICollection<U>> SelectListAsync<T, U>(
Expression<Func<T, bool>> search,
Expression<Func<T, U>> select) where T : class
{
var query =
_context.Set<T>()
.Where(search)
.Select(select);
return await query.ToListAsync();
}
you can call it by passing mapping expression to select property.
Your expression should take IIGrouping<T, Article> as first argument (where T is a type of CategoryId). Assuming that CategoryId is int expression can be written like
public static Expression<Func<IGrouping<int, Article>, ArtDto>> SelectExpression()
{
return x => new ArtDto
{
No = x.Select(c => c.NUMBER).FirstOrDefault(),
UserName = x.Key,
Count = x.Count()
};
}

Linq "join" with a IList<T> getting "Error Unable to create a constant value.."

I have a Save Method that saves with a Linq query a manually re-orderd list (in a web form) that is passed as the parameter to my method, and I try to update the Order Property of the IEnumerable<VM_CategoryLabel> I retrieve from the database (EF) with the corresponding value in the list (maybe would that be clearer with my code below):
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
//Order = x.Sequence_Cat,
Order = (int)listTemplate.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
I used the "test" var to see if my "sub-query" gets the correct value, and it does, but when I use my Linq expression inside the Select (the commented Order line), I get the following error:
Unable to create a constant value of type 'Namespace.Models.VM_CategoryLabelExtra. "Only primitive types and enumeration types are supported in this context.
Here are my classes:
public class VM_CategoryLabel
{
public int Id { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
public class VM_CategoryLabelExtra
{
public int Id { get; set; }
public int IdCat { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
So I suppose that I should not query the list inside my query ? So how do I "match" the 2 lists of values ?
I also tried the following (after having replace in the Linq query: Order = x.Sequence_Cat)that is not working neither because the iteration variable is
read-only:
foreach (var item in requete)
{
item.Order = listTemplate.Where(x => x.Id == item.Id).Select(x => x.Order).FirstOrDefault();
}
try
{
context.SaveChanges();
I suggest using this.
It is the let clause.
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
let list = listTemplate
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
Order = list.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
edit: instead offrom you can just do let list = listTemplate
Should work now :)
example for let:
// The let keyword in query expressions comes in useful with subqueries: it lets
// you re-use the subquery in the projection:
from c in Customers
let highValuePurchases = c.Purchases.Where (p => p.Price > 1000)
where highValuePurchases.Any()
select new
{
c.Name,
highValuePurchases
}
If you do not know how Let working than please download LinqPad and see an example

IList<T> in multi map/reduce result?

I'm struggling with RavenDB's multi map/reduce concept and recently asked this question regarding how to properly write a multi map/reduce index.
I got the simple index in that question working but when I tried to make it a bit more complicated, I cannot make it work. What I want to do is to have the result of the index to contain a list of string, i.e:
class RootDocument {
public string Id { get; set; }
public string Foo { get; set; }
public string Bar { get; set; }
public IList<string> Items { get; set; }
}
public class ChildDocument {
public string Id { get; set; }
public string RootId { get; set; }
public int Value { get; set; }
}
class RootsByIdIndex: AbstractMultiMapIndexCreationTask<RootsByIdIndex.Result> {
public class Result {
public string Id { get; set; }
public string Foo { get; set; }
public string Bar { get; set; }
public IList<string> Items { get; set; }
public int Value { get; set; }
}
public RootsByIdIndex() {
AddMap<ChildDocument>(
children => from child in children
select new {
Id = child.RootId,
Foo = (string)null,
Bar = (string)null,
Items = default(IList<string>),
Value = child.Value
});
AddMap<RootDocument>(
roots => from root in roots
select new {
Id = root.Id,
Foo = root.Foo,
Bar = root.Bar,
Items = root.Items,
Value = 0
});
Reduce =
results => from result in results
group result by result.Id into g
select new {
Id = g.Key,
Foo = g.Select(x => x.Foo).Where(x => x != null).FirstOrDefault(),
Bar = g.Select(x => x.Bar).Where(x => x != null).FirstOrDefault(),
Items = g.Select(x => x.Items).Where(
x => x != default(IList<string>).FirstOrDefault(),
Value = g.Sum(x => x.Value)
};
}
}
Basically I tried to set the Items property to default(IList) when mapping the ChildDocuments and to value of the RootDocument's Items property. This doesn't work, however. It gives the error message
Error on request Could not understand query:
-- line 2 col 285: invalid Expr
-- line 2 col 324: Can't parse double .0.0
when uploading the index. How do I handle lists in multi map/reduce indexes?
Don't use List in your indexes, instead, use arrays.
AddMap<ChildDocument>(
children => from child in children
select new {
Id = child.RootId,
Foo = (string)null,
Bar = (string)null,
Items = new string[0],
Value = child.Value
});
AddMap<RootDocument>(
roots => from root in roots
select new {
Id = root.Id,
Foo = root.Foo,
Bar = root.Bar,
Items = root.Items,
Value = 0
});
Reduce =
results => from result in results
group result by result.Id into g
select new {
Id = g.Key,
Foo = g.Select(x => x.Foo).Where(x => x != null).FirstOrDefault(),
Bar = g.Select(x => x.Bar).Where(x => x != null).FirstOrDefault(),
Items = g.SelectMany(x=>x.Items),
Value = g.Sum(x => x.Value)
};
David, you need to understand that RavenDB stores its indexes with Lucene.NET. That means, you cannot have any complex .net type inside your index.
In your example, I suggest you use a simple string instead of IList<string>. You can then join your string-items:
AddMap<ChildDocument>(
children => from child in children
select new {
Id = child.RootId,
Foo = (string)null,
Bar = (string)null,
Items = (string)null,
Value = child.Value
});
AddMap<RootDocument>(
roots => from root in roots
select new {
Id = root.Id,
Foo = root.Foo,
Bar = root.Bar,
Items = string.Join(";", root.Items),
Value = 0
});

Categories