I have an existing LINQ query that I am trying to optimize. I have the following entity Types (simplified)
public class Account
{
public int Id { get; set; }
public IEnumerable<OpportunityInfo> Opportunities { get; set; }
}
public class Opportunity
{
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public IEnumerable<Quote> Quotes { get; set; }
}
public class Quote
{
}
It is a standard hierarchy of Account to Opportunity to Quote. Nothing Special. I have the following query that I am using on an ASP.NET Core controller index method. I am starting from Quote and working backwards because there is dynamic query logic between the query and opportunityQuotes that must be Quote based. Otherwise I would start from the top direction.
var query = from o in Quotes select o;
additional query logic (filtering and sorting)
var opportunityQuotes = from o in query
group o by new
{
accountId = o.Opportunity.AccountId,
accountName = o.Opportunity.Account.Name,
active = o.Opportunity.Account.Active,
}
into p
select new
{
Id = p.Key.accountId,
Name = p.Key.accountName,
Active = p.Key.active,
Opportunities =
(from q in p
group q by new
{
Id = q.Opportunity.Id,
Name = q.Opportunity.Name,
Active = q.Opportunity.Active
}
into r
select new
{
Name = r.Key.Name,
Id = r.Key.Id,
Active = r.Key.Active,
Quotes = r
})
};
opportunityQuotes.Dump();
This query generates the following SQL.
SELECT [o].[Id], [o].[ARRValue], [o].[AccountId], [o].[AdjustedArr], ...
FROM [Quotes] AS [o]
LEFT JOIN [Opportunities] AS [o.Opportunity] ON [o].[OpportunityId] = [o.Opportunity].[Id]
INNER JOIN [Accounts] AS [o.Account] ON [o].[AccountId] = [o.Account].[Id]
ORDER BY [o].[AccountId], [o.Account].[Name], [o.Account].[Active]
GO
SELECT [q.Opportunity0].[Id], [q.Opportunity0].[Name], [q.Opportunity0].[Active]
FROM [Opportunities] AS [q.Opportunity0]
GO
SELECT [q.Opportunity0].[Id], [q.Opportunity0].[Name], [q.Opportunity0].[Active]
FROM [Opportunities] AS [q.Opportunity0]
GO
SELECT [q.Opportunity0].[Id], [q.Opportunity0].[Name], [q.Opportunity0].[Active]
FROM [Opportunities] AS [q.Opportunity0]
GO
In reality it generates on query for each opportunity, but I left that out for brevity sake. In my opinion EF should not generate a separate query for each quote. In fact if I comment out the .Name and .Active key parameters in the query as shown below:
group q by new
{
Id = q.Opportunity.Id,
// Name = q.Opportunity.Name,
// Active = q.Opportunity.Active
}
and comment out the correspond variables in the select clause it generates much cleaner sql.
SELECT [o].[Id], [o].[ARRValue], [o].[AccountId], ...
FROM [Quotes] AS [o]
LEFT JOIN [Opportunities] AS [o.Opportunity] ON [o].[OpportunityId] = [o.Opportunity].[Id]
INNER JOIN [Accounts] AS [o.Account] ON [o].[AccountId] = [o.Account].[Id]
ORDER BY [o].[AccountId], [o.Account].[Name], [o.Account].[Active]
GO
The reason I am confused is that .Name and .Active are in the exact same object, they are grouped in the key in the same way as the .Id field, and therefore I don't see why EF would change its behavior just by adding additional group values. Can someone explain the behavior?
Let's take a step back and look at it from a different perspective: If you were to write the SQL query manually, and wanted to fetch all the data required in one query, you would get a lot of duplicate data for the opportunities and account. You could also do this here:
var query = from o in Quotes select o;
var oppQuotes = from o in query
select new
{
AccountId = o.Opportunity.Account.Id,
AccountName = o.Opportunity.Account.Name,
// ... and so on, with all the fields you expect to use.
OpportunityId = o.Opportunity.Id,
OpportunityName = o.Opportunity.Name,
// ... and so on, with all the fields you expect to use.
QuoteId = o.Id,
QuoteName = o.Name,
// ... and again, you get the point.
};
Then, just do an .AsEnumerable() on it, and perform the grouping in your C# code. The database won't be able to optimize anything anyways.
var opportunityQuotes = from q in oppQuotes.AsEnumerable()
group q by new { q.AccountId, q.AccountName }
into accounts
// ... and so on.
For your question, why EF is creating the strange query, I'm at a loss.
In any case, it is always good to be thinking about how YOU would create the sql code to get the data you want most efficiently and not rely on EF to "do the right thing". In many cases it will, in others it will completely blow up in your face. When you want a query, think of the SQL and then translate that to EF code. If you tell it specifically, what you want, you will get it.
Related
var queryInfo = (from p in table1
join q in table2 on p.TABLEID equals q.USERNAME
join b in table3 on p.ORIGINAL_USER equals b.USERNAME
where p.NAME == IdVal
select new
{
p.NAME,
p.ID,
p.EXCHANGE,
p.CREATION,
q.USERNAME,
q_email = q.EMAIL,
q_fullname = q.FULL_NAME,
b_email = b.EMAIL,
p.ORIGINAL_USER,
b_fullname = b.FULL_NAME
});
Name = queryInfo.ToList().ElementAt(0).ToString();
ID = queryInfo.ToList().ElementAt(1).ToString();
exchange = queryInfo.ToList().ElementAt(2).ToString();
Creation = queryInfo.ToList().ElementAt(3).ToString();
AUsername = queryInfo.ToList().ElementAt(4).ToString();
AEmail = queryInfo.ToList().ElementAt(5).ToString();
AFullName = queryInfo.ToList().ElementAt(6).ToString();
EEmail = queryInfo.ToList().ElementAt(7).ToString();
EUsername = queryInfo.ToList().ElementAt(8).ToString();
EFullName = queryInfo.ToList().ElementAt(9).ToString();
The query is correct and working, I'm having problem trying to select and assign one to each declared variable.
I tried
queryInfo.ToList().ElementAt(0).ToString();
but this is not working. What is the proper syntax?
Create a custom class so you can map your resut into
class:
public class User
{
public string Name { get; set; }
public string ID { get; set; }
public string exchange { get; set; }
public string Creation { get; set; }
public string AUsername { get; set; }
public string AEmail { get; set; }
public string AFullName { get; set; }
public string EEmail { get; set; }
public string EUsername { get; set; }
public string EFullName { get; set; }
}
mapping:
User result = (from p in table1
join q in table2 on p.TABLEID equals q.USERNAME
join b in table3 on p.ORIGINAL_USER equals b.USERNAME
where p.NAME == IdVal
select new User()
{
Name = p.NAME,
ID = p.ID,
exchange = p.EXCHANGE,
Creation = p.CREATION,
AUsername = q.USERNAME,
AEmail = q.EMAIL,
AFullName = q.FULL_NAME,
EEmail = b.EMAIL,
EUsername = p.ORIGINAL_USER,
EFullName = b.FULL_NAME
}).FirtstOrDefault();
Materialize query via FirstOrDefault() and retrieve property values:
var queryInfo =
(from p in table1
join q in table2 on p.TABLEID equals q.USERNAME
join b in table3 on p.ORIGINAL_USER equals b.USERNAME
where p.NAME == IdVal
select new
{
p.NAME,
p.ID,
p.EXCHANGE,
p.CREATION,
q.USERNAME,
q_email = q.EMAIL,
q_fullname = q.FULL_NAME,
b_email = b.EMAIL,
p.ORIGINAL_USER,
b_fullname = b.FULL_NAME
})
.FirstOrDefault();
Name = queryInfo?.NAME;
ID = queryInfo?.ID.ToString();
exchange = queryInfo?.EXCHANGE;
Creation = queryInfo?.CREATION.ToString();
AUsername = queryInfo?.USERNAME;
AEmail = queryInfo?.q_email;
AFullName = queryInfo?.q_fullname;
EEmail = queryInfo?.b_email;
EUsername = queryInfo?.ORIGINAL_USER;
EFullName = queryInfo?.b_fullname;
First some background information
Your code is very inefficient.
queryInfo is an IQueryable<...>, meaning that it holds a query: the potential to fetch some data. It does not hold the data itself.
For this, the IQueryable holds an Expression and a Provider. The Expression represents the query in some generic format. The Provider knows who should execute this query (usually a database management system) and what language is used to communicate with this DBMS.
As long as you concatenate LINQ methods that return IQueryable<TResult>, only the Expression changes. The query is not executed, there is no communication with the DBMS. Concatenating this kind of LINQ methods is efficient.
IQueryable also implements IEnumerable. This means, that to execute the query and to enumerate the fetched sequence, at its lowest level you use GetEnumerator() to get the enumerator, and repeatedly call MoveNext() / Current to access the enumerated element:
IQueryable<TResult> query = dbContext.Students.Where(...).OrderBy(...);
// execute the query:
using (IEnumerator<TResult> enumerator = query.GetEnumerator())
{
// and enumerate the fetched data:
while (enumerator.MoveNext())
{
// There is another element, process it:
TResult fetchedElement = enumerator.Current;
ProcessFetchedElement(fetchedElement);
}
}
Well, this is a lot of code. Usually we use high level methods, which deep inside will call GetEnumerator() / MoveNext() / Current:
// execute the query and process the fetched data:
foreach (TResult fetchedElement in query)
{
ProcessFetchedElement(fetchedElement);
}
All LINQ methods that return IQueryable<...> will not execute the query. The other LINQ methods (= the ones that return List<TResult>, TResult, Boolean, etc, anything not IQueryable) will call foreach or deep inside GetEnumerator / MoveNext / Current. These other methods will contact the database to execute the query.
What does this have to do with my question?
Let's look at your code:
var queryInfo = ...
// queryInfo is an IQueryable. The query is not executed yet!
Name = queryInfo.ToList().ElementAt(0).ToString();
// ToList will execute the query and put all data in a List,
// from this List you take the first element and call ToString()
ID = queryInfo.ToList().ElementAt(1).ToString();
// ToList will execute the query again and put all data in a second List,
// from this List you take the second element and call ToString()
etc. You execute the query 10 times. You join the three tables 10 times, you keep only the one with p.Name equal to IdVal and send the remaining data to your process. You do this 10 times.
It would be much more efficient to do this only once:
// execute the query once and put all fetched data in a List
var fetchedData = queryInfo.ToList();
// access the fetched data. Since it is a List, we can use indexes
Name = fetchedData[0].ToString();
Id = fetchedData[1].ToString();
exchange = fetchedData[2].ToString();
Creation = fetchedData[3].ToString();
I don't think that is what you want.
If I look closer at your query, then I see that you join three tables:
var queryInfo = (from p in table1
join q in table2 on p.TABLEID equals q.USERNAME
join b in table3 on p.ORIGINAL_USER equals b.USERNAME
From the joined table (=sequence of rows), you keep only those rows that have p.Name == idVal:
where p.NAME == IdVal
There might be one such element, there might be more, or maybe none.
From each remaining row, you make one new object:
select new
{
p.NAME,
p.ID,
p.EXCHANGE,
...
});
As said before: the result is a query. The query represents the potential to fetch a sequence of objects with properties Name, Id, Exchange, ....
I think, that you want the Name / Id / Exchange / etc from the first element of the sequence
If that is the case, we don't have to fetch all elements, we only need to fetch the first element (if there is one)
var queryInfo = ...
// execute the query, and ask only for the first element.
// In SQL this is something like SELECT TOP 1 ... FROM ...
var fetchedElement = queryInfo.FirstOrDefault();
Now if your query yields one or more elements, you will have only the first one. However, if your query results in an empty sequence, fetchedElement will be null
if (fetchedElement != null)
{
// There is an element with Name == IdVal
Name = fetchedElement.Name;
Id = fetchedElement.Id;
...
}
else
{
// There is no such element; TODO: report to operator?
}
Be aware, that it if the query yields more than one element, it is not guaranteed what the first element of your sequence might be. Therefore, if you expect that in some cases there might be more than one element, consider to order the sequence, so the first element is defined. For instance order by ascending creation date. Sometimes the DBMS does not accept a FirstOrDefault of an unordered sequence.
I have a viewmodel that has a header property, and then an associated child collection as such:
public int ProjectApprovalHeaderId { get; set; }
public List<ProjectApprovalStepsVM> ProjectApprovalHistorySteps{ get; set; }
I have a query that returns the data as follows:
ProjectApprovalHeaderId StepName Status
1 Step1 A
1 Step2 C
1 Step3 A
2 Step1 D
I'm trying to write the LINQ so that I can populate the viewmodel with the HeaderId and then the child collection with the actual step data but I'm stuck. I know this isn't much, but it's where I'm at:
var approvalHistory = from s in _context.ProjectApprovalSteps
join u1 in _context.Users on s.AssignedApproverID equals u1.ID
join p in _context.Projects on s.ProjectID equals p.ID
join sc in _context.ApprovalStepStatusCodes on s.Status equals sc.StatusCode into statusList
from scd in statusList.DefaultIfEmpty()
where s.ProjectID == projectId
orderby s.ProjectApprovalHeaderID, s.Sequence
select new ProjectApprovalHistoryVM
{
ProjectApprovalHeaderId = s.ProjectApprovalHeaderID,
ProjectApprovalHistorySteps =
};
vmApprovalHistory.ProjectApprovalHistorySteps = approvalHistory.ToList();
Hoping someone can point me in the right direction.
I'm a fan of Dapper, you can install via Nuget Install-Package Dapper. The reason I recommend, is the multi mapping capabilities. So an object like you have, has nested objects that are needed to be populated and filtered.
public IEnumerable<SpeciesModel> GetAllSpecies() => dbConnection
.Query<SpeciesModel, SpeciesCategoryModel, SpeciesTypeModel, WetlandIndicatorModel, SpeciesModel>(getAllSpeciesQuery,
(species, speciesCategory, speciesType, wetlandIndicator) =>
{
species.SpeciesCategory = speciesCategory;
species.SpeciesType = speciesType;
species.WetlandIndicator = wetlandIndicator;
return species;
});
So the above syntax is expressing the nested object. As your query is returned, it'll also associate your child objects. I believe that is your intent, plus this saves you a lot of time.
More complex example with a collection as a nested object with Dapper.
public IEnumerable<StemModel> GetStemModelForPlotHeader(int projectParameter, int plotParameter,
string plantCommunityParameter) =>
dbConnection.Query<StemModel, PlotSurveyModel, PlantCommunityModel, ProjectModel, SpeciesModel, StemModel>(getAllPlotHeaderDetails,
(stem, survey, plantCommunity, project, species) =>
{
stem.PlotSurvey = survey;
survey.PlantCommunity = plantCommunity;
survey.Project = project;
stem.Species = species;
return stem;
},
new
{
ProjectId = projectParameter,
PlantCommunityCode = plantCommunityParameter,
PlotNumber = plotParameter
});
Update:
Please note the above answer would require you to replace your data access layer. I'm impartial to Dapper, since you receive the performance with the extra mapping functionality. Especially for simple queries, but you can leverage the performance with extra mapping capabilities. Also it forces your data calls to be SQL oriented, not code generating dynamic queries.
I have a model called ElectricityBillSiteExceeding that looks like this:
public class ElectricityBillSiteExceeding
{
public string GroupInvoiceNumber { get; set; }
public int ElectricityBillMainId { get; set; }
public string SiteNo { get; set; }
public decimal BillSiteTotal { get; set; }
public decimal MaximumAmount { get; set; }
}
I want to create a list of this type and use it to feed a grid on one of my pages, the purpose is to show which site has bills that exceed the max amount allowed.
I have written the SQL which will give me this dataset, it looks like this:
SELECT SUM(ElectricityBillSiteTotal),
ebs.ElectricityBillMainId,
SiteNo,
ebm.GroupInvoiceNumber,
es.MaximumAmount
FROM dbo.ElectricityBillSites ebs
LEFT JOIN dbo.ElectricityBillMains ebm
ON ebs.ElectricityBillMainId = ebm.ElectricityBillMainId
LEFT JOIN dbo.ElectricitySites es
ON ebs.SiteNo = es.SiteNumber
GROUP BY ebs.ElectricityBillMainId, SiteNo, ebm.GroupInvoiceNumber, es.MaximumAmount
HAVING SUM(ElectricityBillSiteTotal) <> 0 AND SUM(ElectricityBillSiteTotal) > es.MaximumAmount
I'm now in my repository trying to write the method which will go to the database and fetch this dataset so that I can power my grid for the user to see.
This is where I'm struggling. I have written a basic LINQ statement to select from a couple of tables, however I'm unsure how I can incorporate the group by and having clause from my SQL and also how I can then turn this IQueryable object into my List<ElectricityBillSiteExceeding> object.
What I have so far
public List<ElectricityBillSiteExceeding> GetAllElectricityBillSiteExceedings()
{
var groupedBillSitesThatExceed = from billSites in _context.ElectricityBillSites
join billMains in _context.ElectricityBillMains on billSites.ElectricityBillMainId equals
billMains.ElectricityBillMainId
join sites in _context.ElectricitySites on billSites.SiteNo equals sites.SiteNumber
//TODO: group by total, mainId, siteNo, GroupInv, MaxAmt and Having no total = 0 and total > max
select new
{
groupInv = billMains.GroupInvoiceNumber,
mainId = billMains.ElectricityBillMainId,
siteNo = billSites.SiteNo,
total = billSites.ElectricityBillSiteTotal,
max = sites.MaximumAmount
};
//TODO: Map the result set of the linq to my model and return
throw new NotImplementedException();
}
Can anyone point me in the right direction here?
The correct Linq query for your sql is the following. See Left Join to understand the DefaultIfEmpty and also the notes there about the use of ?. in the following group by.
(About the having - in linq you just provide a where after the group by)
var result = from ebs in ElectricityBillSites
join ebm in ElectricityBillMains on ebs.ElectricityBillMainId equals ebm.ElectricityBillMainId into ebmj
from ebm in ebmj.DefaultIfEmpty()
join es in ElectricitySites on ebs.SiteNo equals es.SiteNumber into esj
from es in esj.DefaultIfEmpty()
group new { ebs, ebm, es } by new { ebs.ElectricityBillMainId, ebs.SiteNo, ebm?.GroupInvoiceNumber, es?.MaximumAmount } into grouping
let sum = grouping.Sum(item => item.ebs.ElectricityBillSiteTotal)
where sum > 0 && sum > grouping.Key.MaximumAmount
orderby sum descending
select new ElectricityBillSiteExceeding
{
GroupInvoiceNumber = grouping.Key.GroupInvoiceNumber,
ElectricityBillMainId = grouping.Key.ElectricityBillMainId,
SiteNo = grouping.Key.SiteNo,
BillSiteTotal = sum,
MaximumAmount = grouping.Key.MaximumAmount
};
The error you get:
An expression tree lambda may not contain a null propagating operator
By reading this I conclude that you have an older versino of the provider and thus replace the group by code from the code above with the following:
let GroupInvoiceNumber = ebm == null ? null : ebm.GroupInvoiceNumber
let MaximumAmount = es == null ? 0 : es.MaximumAmount
group new { ebs, ebm, es } by new { ebs.ElectricityBillMainId, ebs.SiteNo, GroupInvoiceNumber, MaximumAmount } into grouping
Before getting into grouping , you need to be aware that the default join in LINQ is always an INNER JOIN. Take a look at the MSDN page How to: Perform Left Outer Joins. However, in the solution I'm presenting below, I'm using INNER JOINs since you are using fields from the other tables in your grouping and having clauses.
For reference on grouping using LINQ, check out How to: Group Query Results on MSDN.
A solution specific to your case is going to look something like:
public List<ElectricityBillSiteExceeding> GetAllElectricityBillSiteExceedings()
{
var qryGroupedBillSitesThatExceed = from billSites in _context.ElectricityBillSites
join billMains in _context.ElectricityBillMains on billSites.ElectricityBillMainId equals billMains.ElectricityBillMainId
join sites in _context.ElectricitySites on billSites.SiteNo equals sites.SiteNumber
where billSites.ElectricityBillSiteTotal != 0 && billSites.ElectricityBillSiteTotal > sites.MaximumAmount
group new { billMains.GroupInvoiceNumber, billMains.ElectricityBillMainId, billSites.SiteNo, billSites.ElectricityBillSiteTotal, sites.MaximumAmount }
by new { billMains.GroupInvoiceNumber, billMains.ElectricityBillMainId, billSites.SiteNo, billSites.ElectricityBillSiteTotal, sites.MaximumAmount } into eGroup
select eGroup.Key;
var inMemGroupedBillSitesThatExceed = qryGroupedBillSitesThatExceed.AsEnumerable();
var finalResult = inMemGroupedBillSitesThatExceed.Select(r => new ElectricityBillSiteExceeding()
{
BillSiteTotal = r.ElectricityBillSiteTotal,
ElectricityBillMainId = r.ElectricityBillMainId,
GroupInvoiceNumber = r.GroupInvoiceNumber,
MaximumAmount = r.MaximumAmount,
SiteNo = r.SiteNo,
});
return finalResult.ToList();
}
This probably will be enough. You could use AutoMapper. It will trivialize mapping to classes.
var resultList = groupedBillSitesThatExceed
.AsEnumerable() //Query will be completed here and loaded from sql to memory
// From here we could use any of our class or methods
.Select(x => new ElectricityBillSiteExceeding
{
//Map your properties here
})
.ToList(); //Only if you want List instead IEnumerable
return resultList;
I am a little weak in LINQ to SQL so will try to explain my problem.
I have a method as follows (simplified to explain it better):
public static List<ic_ProductData> GetCompleteSimilarProductsWithApplyButton(InfoChoiceAdminDataContext db)
{
var products = (from
p in db.ic_ProductDatas
join proddef in db.ic_ProductDefs on p.ProductDefId equals proddef.ProductDefId
select p
).ToList();
return products;
}
ic_ProductData and ic_ProductDefs are tables in my database
The ic_ProductData class contains a manually created property as:
public ic_ProductDef RelatedProductDef { get; set; }
I want to modify the above LINQ to SQL query so that I can populate this property.
Please note I do not want another call to the database.
Also there are a lot of properties in ic_ProductData so I want to avoid mapping each and every property
Something to the effect of the following (obviously the below is wrong):
public static List<ic_ProductData> GetCompleteSimilarProductsWithApplyButton(InfoChoiceAdminDataContext db)
{
var products = (from
p in db.ic_ProductDatas
join proddef in db.ic_ProductDefs on p.ProductDefId equals proddef.ProductDefId
//trying to change here
select new ic_ProductData
{
//do something with p here so that all the properties of new object gets filled
// avoid mapping of properties here
RelatedProductDef = proddef
}
).ToList();
return products;
}
With my limited knowledge I am stuck here.
Please help!
Thanks in advance!
You can do something like this:
var query = (from p in db.ic_ProductDatas
join proddef in db.ic_ProductDefs on p.ProductDefId equals proddef.ProductDefId
select new
{
ProductData = p,
Def = proddef
}).ToList();
List<ic_ProductData> products = new List<ic_ProductData>();
foreach( var product in query)
{
product.ProductData.RelatedProductDef = product.Def;
products.Add(product);
}
Basicly, you first need to do the one query to the database, this returns an anonymous type containing both your product and its Def.
Finally, you loop (in memory, no db-calls!) over these, creating your final objects with their RelatedProductDef properties populated.
What's the equivalent LINQ instruction for a Datatable of the following SQL query:
SELECT code_direction, count(TP) AS CN
FROM table1
WHERE cod_time = 'A011'
GROUP BY TP,code_direction;
and how to get the result into a new datatable?
I tried to convert it but I there're some errors. Someone could take a look on this:
var query = from t in table1.AsEnumerable()
group t by new { t.TP, t.code_direction }
into grp
select new
{
grp.Key.code_direction,
CN = grp.Count(t.TP)
};
foreach (var x in query)
{
Console.Write(x.code_direction);
Console.Write(x.CN);
}
As far as your first question goes. The LINQ equivalent of the SQL query is:
var query = from t in table1.AsEnumerable()
where t.cod_time == "A011"
group t by new { t.TP, t.code_direction }
into grp
select new
{
grp.Key.code_direction,
CN = grp.Count()
};
Note that you don't have to pass any argument to grp.Count(). (For the obvious reason that in SQL COUNT(TP) is the same as COUNT(*), i.e. just count the number of rows. The story would be different if you'd use COUNT(DISTINCT TP) or similar.)
As far as the second question goes, if your query just returned an IEnumerable<T> where T is DataRow (i.e. a query like table1.AsEnumerable().Where(r => r.cod_time == "A011")) then you could just the DataTableExtensions.CopyToDataTable extension method. As your query returns an anonymous type however, you will have to follow these instructions found on MSDN.
I Have been using LINQ to work on a JSON object returned from a remote sharepoint web service. I have posted this because most of the answers I found online were slightly different from what I needed.
a json list of daily activities is returned from a remote sharepoint list & is then summarised using LINQ
The simplified version of a custom object definition is shown below( & which is defined in the models area of an MVC application)
public class MyCustomObjectList
{
public string eventdate { get; set; }
public string userid { get; set; }
public string action { get; set; }
}
The JSON object is serialised into a MyCustomObjectList array.
var customobject = serializer.Deserialize<MyCustomObjectList>(jsonobject);
I wanted to work out how many actions of each type happened on a given day. NB eventdate is stored as a string in format yyyy-mm-dd hh:MM:ss. This was to simplify conversions between c#, JSON & Jquery ( where required I create DateTime objects elsewhere in the code using the
eventdate.
Some will argue this is inefficient, but I prefer to split processes into a sequential set of really simple operations, for the sake of easier debugging & to help other people follow my code. Thats why there are 2 Linq queries .
querya strips out the time component from the eventdate This ensures our later grouping happens by day, & not by second. To be doubly sure that there is no caching, I create it in a new field called actionday. I also rename action to activity, because intellisense was getting confused!! The other columns are copied as is.
var querya =
from c in customobject.rows
select new { actionday = c.eventdate.Substring(0, 10), activity = c.action, c.userid,
c.eventdate };
/* queryb produces a grouped count of querya, grouped on actionday & activity, creating new columns actionkey,ActionCount,Dte,action & DetailList ( which is a summary for debugging purposes)
*/
var queryb=
from p in querya group p by new { p.actionday, p.activity} into idGroup
actionkey = idGroup.Key,
ActionCount = idGroup.Count(),
Dte = idGroup.Key.actionday,
action = idGroup.Key.activity,
DetailList = idGroup
};
Here’s a version that sumarises by 3 columns
var queryc = from p in querya
group p by new { p.actionday, p.userid, p.activity} into idGroup
select new
{
actionday = idGroup.Key,
ActionCount = idGroup.Count(),
userid = idGroup.Key.userid,
Dte = idGroup.Key.actionday,
action = idGroup.Key.activity,
DetailList = idGroup
};