I am aggregating data that I retrieve from multiple identical web services. The same row count and data points are returned with only a variance in the Value. The GroupBy clause I am using is not condensing any of the rows. I have the same row count before and after the GroupBy.
MyWebServiceUrls
.AsParallel()
.SelectMany(url => GetMetricItemData(url))
.GroupBy(item => new { item.DateTime, item.Group, item.Metric }, item => item.Value)
.Select(grp => new MetricItem()
{
DateTime = grp.Key.DateTime,
Group = grp.Key.Group,
Metric = grp.Key.Metric,
Value = // type = decimal?
grp.Any(mi => mi.HasValue)
? grp.Key.Metric.AggregationType == Metric.MetricAggregationTypes.Sum
? grp.Sum(mi => mi.Value)
: grp.Average(mi => mi)
: null
})
.AsEnumerable();
The syntax looks correct based on other examples I have found.
I send this data back to my database and can aggregate with the statement GROUP BY [DateTime], [Group], [Metric] and everything works great. While I can use the database to solve this issue, I would like to know how to correctly use LINQ in this instance.
What am I missing to get this LINQ expression to work?
UPDATE:
This is the relevant MetricItem and Metric class definition:
public class MetricItem
{
public string Group { get; set; }
public DateTime DateTime { get; set; }
public Metric Metric { get; set; }
public Decimal? Value { get; set; }
}
public class Metric
{
public string Code { get; set; }
public string Label { get; set; }
private List<string> SumMetrics = new List<string>(new string[] { "TPI", "TPO", "TPIO" });
public enum MetricAggregationTypes { Sum, Average };
public MetricAggregationTypes AggregationType
{
get
{
if (SumMetrics.IndexOf(this.Code) >= 0)
return MetricAggregationTypes.Sum;
else
return MetricAggregationTypes.Average;
}
}
}
You need to override Equals and GetHashCode on the Metric class. Most Linq methods use hash codes for comparison operations, so for most objects you define yourself, you need to override this class if you plan to use something like GroupBy, Union, etc.
Related
I have a function that return a linq result :
private IEnumerable<object> prepareData()
{
var data = from res in Globals.ds.Tables[0].AsEnumerable()
.GroupBy(x => new
{
art = x.Field<string>("artiste"),
alb = x.Field<string>("album"),
})
.Select(p => new
{
album = p.Key.alb,
artiste = p.Key.art,
count_lab = p.Count(),
lab = p.Select(x => x.Field<string>("label")).First(),
filp = p.Select(x => x.Field<string>("file_path")).First()
})
.OrderBy(x => x.lab)
select res;
return data;
}
The query works well as designed, i can do data = PrepareData(); and get the right results.
My issue is when i want to do a .where on the data.
if i do :
var album = data.Where(x => x.
Then i dont have any option to select a single field (it's the same if i want to do a .Select()).
I tried data.AsEnumerable() before but to no success.
I'm thinking the IEnumerable<object> prepareData() is the culprit, but i have no idea how to fix this (if ever it's the case).
I need help
Thanks in advance
If you want to select a single field you can use: First() or FirstOrDefault(). Difference between this two is:
First() will throw an exception if an element is not found.
FirstOrDefault() will return null if element is not found.
Also if you want to fix the problem with IEnumerable<object> you need to create an DTO class where you can map all items from select.
Something like this:
public class DTOClass
{
public string album { get; set; }
public string artiste { get; set; }
public string count_lab { get; set; }
public string lab { get; set; }
public string filp { get; set; }
}
And then in select you can simply do:
...
Select(p => new DTOClass {
// map the values for DTO class here
}
I'm a little lost here. I've tried multiple different methods for returning this list of names I have but I can't seem to return them in the correct alphabetical order. This is what I've got:
[HttpGet]
[Queryable(PageSize=150)]
public IQueryable<BoyName> GetBoyNames(string letter)
{
List<BoyName> names = Db.BoyNames.Where(c => c.Name.StartsWith(letter)).OrderBy(x => x.Name).ToList();
return names.AsQueryable();
}
And my model:
public partial class BoyName
{
public int Id { get; set; }
public string Name { get; set; }
public string Meaning { get; set; }
public string Origin { get; set; }
}
It is giving me names which is nice...but I can't seem to get them to display in order.
As pointed out in the comments above, calling AsQueryable() on your list object may not preserve the ordering of the list. Rather than converting the result of your LINQ query to a List and then calling AsQueryable, just return the result of your LINQ query since it is a valid return type for your method (IQueryable<BoyName>).
var names = Db.BoyNames
.Where(c => c.Name.StartsWith(letter))
.OrderBy(x => x.Name);
return names;
I have a Comment and Votes related to the comment.
[Table("QAComment")]
public class QaComment : IEntity
{
[Key, Column("QACommentID")]
public int Id { get; set; }
// ...
public virtual ICollection<QaCommentVote> Votes { get; set; }
[NotMapped]
public int OverallVote { get; set; }
}
[Table("QACommentVote")]
public class QaCommentVote : IEntity
{
[Key, Column("QACommentVoteID")]
public int Id { get; set; }
[ForeignKey("QAComment")]
public int QaCommentId { get; set; }
public int Value { get; set; }
public virtual QaComment QaComment { get; set; }
}
I need to get comments with the sum of their votes, not pulling all votes to the application.
The ways I can see to achive this:
1. Make a database view for Commment and calc votes sum in there.
Cons: dont wanna make extra-views
2. Via LINQ:
var comments =
Set<QaComment>()
.Select(c => new QaComment() {/* assign every property once again and calc OverallVote */});
Cons: don't like to assign allproperties once again.
Is there a better way devoid of that cons?
UPDATE
This is what I want as a result of LINQ:
SELECT
qac.*,
(SELECT SUM(v.Value)
FROM QACommentVote v
WHERE v.QACommentID = qac.QACommentID) as OverallVote
FROM QAComment qac
You can fetch QaComment and the sum you're looking for separately as anonymous type and merge them into one object using LINQ to Objects:
var comments
= Set<QaComment>()
.Select(c => new { c, sum = c.Votes.Sum(v => v.Value))
.AsEnumerable() // to make next query execute as LINQ to Objects query
.Select(x => { x.c.OverallVote = x.sum; return x.c; })
.ToList();
But to make point clear: I haven't tested that :)
I have two classes Teams and PlayerTeams
public class PlayerTeams
{
public int ID { get; set; }
public string PlayerName { get; set; }
public string PlayerCountry { get; set; }
public bool IsActive { get; set; }
public string PlayerTeam { get; set; }
}
public class Players
{
public int ID { get; set; }
public string Name { get; set; }
public string Country { get; set; }
public bool? Status { get; set; }
}
I have a list of PlayerTeams which is grouped by PlayerTeam like this.
var groupedPlayers = teams
.OrderBy(x => x.PlayerName)
.GroupBy( x => x.PlayerTeam)
.ToList();
Its of type List<IGrouping<string, PlayerTeams>> but I want it to be of type List<IGrouping<string, Players>> as I do not want the redundant key information on every row.
How could I possibly achieve that? I could only think of something like .ConvertAll() on the IGrouping. I am not able to make it also.
Is there an efiicient way to do this?
If you can change the grouping, I'd just use:
var groupedPlayers = teams
.OrderBy(x => x.PlayerName)
.GroupBy(x => x.PlayerTeam, Players.FromPlayerTeam)
.ToList();
Where Players.FromPlayerTeam is a static method in Players which takes a PlayerTeam and returns a Players.
Additionally, I'd suggest using ToLookup instead of GroupBy - a Lookup is exactly what you want here, without bothering with the ToList call.
This not testet, just an idea.
If you have trouble converting your linq statement, which is expecting the IGrouping type, to a string list, then you might have to select it before.
var groupedPlayers = new List<string>();
groupedPlayers = teams
.OrderBy(x => x.PlayerName)
.GroupBy(x => x.PlayerTeam, Players.FromPlayerTeam)
.Select(x => x.Key) // << added select
.ToList();
I have a simple model, consisting of a document that references one or more article using a reference object (this is because in the domain, we do not own the articles so we can only reference them).
I'm trying to write a query that lists the documents, printing the ID and a string consisting of a comma separated list of article numbers. For example:
ID ARTICLES
------------------
1 ACC, PE2,
2 ER0, AQ3, FEE
3 PE2
My problem is with selecting the comma separated list.
Here are the domain classes:
// The Entity class has an Id property.
public class Document : Entity
{
public virtual IEnumerable<ArticleReference> ArticleReferences { get; set; }
public virtual DateTime ReceiveDate { get; set; }
}
// The ValueObject does not have an Id property ofcourse.
public class ArticleReference : ValueObject
{
public virtual string ArticleNumber { get; set; }
public virtual string ArticleName { get; set; }
}
The article reference is a value object so it does not have an ID of its own.
This is the view model that represents an item in the result list:
public class DocumentListItemModel
{
public int Id { get; set; }
public string ArticleNumbers { get; set; }
public string ReceiveDate { get; set; }
}
And here's the query class I have come up with so far:
public class DocumentQuery
{
public IList<DocumentListItemModel> ExecuteQuery()
{
IntermediateModel model = null;
ArticleReference articleReferenceAlias = null;
return Session
.QueryOver<Document>()
.JoinAlias(n => n.ArticleReferences, () => articleReferenceAlias);
.SelectSubQuery(
QueryOver.Of<ArticleReference>(() => articleReferenceAlias)
// There is no way of matching references to documents from a domain
// point of view since the references are value objects and
// therefore don't have an ID.
.Where(n => ...)
.Select(q => articleReferenceAlias.Number))
.WithAlias(() => model.ArticleNumbers)
.TransformUsing(Transformers.AliasToBean<IntermediateModel>());
.Future<IntermediateModel>()
.ToList()
.Select(n =>
new DocumentListItemModel()
{
Id = n.Id,
ArticleNumbers = string.Join(", ", n.ArticleNumbers.OrderBy(p => p)),
ReceiveDate = n.ReceiveDate.ToString("d", CultureInfo.CurrentCulture)
})
.ToList();
}
private class IntermediateModel
{
public int Id { get; set; }
public IEnumerable<string> ArticleNumbers { get; set; }
public DateTime ReceiveDate { get; set; }
}
}
As you can see, I can't express the .Where statement because there is no way of matching references to documents from a domain point of view. The references are value objects and therefore don't have an ID.
The question is: how do I fix the query to properly select the list of article numbers so I can use it in my string.Join statement to make the comma separated string?
I think you are taking the definition of value object too literally. Assigning a surrogate identifier (identity column, Guid, etc.) to a value object does not make it any less of a value object. It's a value object because its equality is based its values, not its identity. This does not require that a value object cannot have an identity, and in practice it almost always has to.
Your application obviously has to be able to link a Document to a set of ArticleReferences and the best way to accomplish that is by adding an ID to ArticleReference.
I managed to solve the problem. This is what I ended up with:
public IList<DocumentListItemModel> ExecuteQuery()
{
ArticleReference articleReferenceAlias = null;
return Session
.QueryOver<Document>()
.JoinAlias(n => n.ArticleReferences, () => articleReferenceAlias,
JoinType.LeftOuterJoin)
.SelectList(list => list
.Select(n => n.Id)
.Select(n => articleReferenceAlias.Number))
.List<object[]>()
.Select(x => new
{
Id = (int)x[0],
ArticleNumber = (string)x[1]
})
.GroupBy(n => n.Id).Select(n =>
{
return new DocumentListItemModel
{
Id = n.First().Id,
ArticleNumbers = string.Join(", ", n.Select(p => p.ArticleNumber))
};
}).ToList();
}
}
I couldn't use the alias-to-bean transformer anymore because it cannot handle collection properties. That's why the solution has the GroupBy, it consolidates the rows, aggregating the article numbers into a string.