From a traditional SQL sentence like this:
SELECT Id, Owner, MIN(CallTime)
FROM traffic
WHERE CallType = "IN"
GROUP BY Owner;
where CallTime is a datetime field, what I want is the oldest record belonging to each Owner.
How can I achieve this with Linq?
This was my attempt (I'm using Entity Framework and context is the entity instance):
var query = context.traffic.Where(t => t.CallType == "IN");
var results = query
.GroupBy(t => t.Owner)
.Select(g => new { CallTime = g.Min(h => h.CallTime) });
But I need also access to Id and Owner fields whereas now I have only access to CallTime.
You cannot access Id in the given code because you are grouping by Owner and the Key to the group will be the Owner not the 'traffic' object.
If you group by traffic objects you need some way to tell the groupBy how to compare them properly (i.e. group by owner) This can be done with an IEqualityComparer
e.g.
private class Traffic {
public int Id { get; set; }
public string Owner { get; set; }
public DateTime CallTime { get; set; }
}
private class TrafficEquaityComparer : IEqualityComparer<Traffic> {
public bool Equals(Traffic x, Traffic y) {
return x.Owner == y.Owner;
}
public int GetHashCode(Traffic obj) {
return obj.Owner.GetHashCode();
}
}
private static TrafficEquaityComparer TrafficEqCmp = new TrafficEquaityComparer();
private Traffic[] src = new Traffic[]{
new Traffic{Id = 1, Owner = "A", CallTime = new DateTime(2012,1,1)}, // oldest
new Traffic{Id = 2, Owner = "A", CallTime = new DateTime(2012,2,1)},
new Traffic{Id = 3, Owner = "A", CallTime = new DateTime(2012,3,1)},
new Traffic{Id = 4, Owner = "B", CallTime = new DateTime(2011,3,1)},
new Traffic{Id = 5, Owner = "B", CallTime = new DateTime(2011,1,1)}, //oldest
new Traffic{Id = 6, Owner = "B", CallTime = new DateTime(2011,2,1)},
};
[TestMethod]
public void GetMinCalls() {
var results = src.GroupBy(ts => ts, TrafficEqCmp)
.Select(grp => {
var oldest = grp.OrderBy(g => g.CallTime).First();
return new { Id = oldest.Id,
Owner = grp.Key.Owner,
CallTime = oldest.CallTime };
}); }
this gives
ID : Owner : MinCallTime
1 : A : (01/01/2012 00:00:00)
5 : B : (01/01/2011 00:00:00)
as the results.
Your SQL query doesn't look valid to me: you're using Id but not grouping by it. I assume that you wanted to group by Id and Owner?
var results = query
.GroupBy(t => new {Id = t.Id, Owner = t.Owner})
.Select(g => new { Id = g.Key.Id, Owner = g.Key.Owner, CallTime = g.Min(h => h.CallTime) })
.ToList();
If you want to get the oldest (smallest) ID instead of grouping by it:
var results = query
.GroupBy(t => t.Owner)
.Select(g => new { Id = g.Min(x => x.Id), Owner = g.Key, CallTime = g.Min(h => h.CallTime) })
.ToList();
// custQuery is an IEnumerable<IGrouping<string, Customer>>
var custQuery =
from cust in customers
group cust by cust.City into custGroup
where custGroup.Count() > 2
orderby custGroup.Key
select custGroup;
In that example you select the group
Related
I have one plain class data like,
var data = new List<PlainData>
{
new PlainData {Name = "A", Owner = "X"},
new PlainData {Name = "A", Owner = "Y"},
new PlainData {Name = "B", Owner = "X"}
};
Here for same Name I have one or more than one owner.
Now I want to transform this data into list based owners like below class,
public class ListBasedData
{
public string Name { get; set; }
public List<string> Owners { get; set; }
}
And here I am trying to do, how to grab all the owners of a name?
List<ListBasedData> listBasedDatas = new List<ListBasedData>();
var groups = data.GroupBy(a => a.Name);
foreach (var group in groups)
{
var a = group.Key;
var b = group.ToList();
listBasedDatas.Add(new ListBasedData{Name = group.Key, Owners = });
}
List<ListBasedData> listBasedDatas = data
.GroupBy(a => a.Name)
.Select(grp => new ListBasedData
{
Name = grp.Key,
Owners = grp.Select(x => x.Owner).ToList()
})
.ToList();
The key is to use Select to perform a projection on the owners in each group, from PlainData to string.
I have a table of WorldEvents. Each WorldEvent has a list of Presentations, that happened in some country, regarding that WorldEvent
public class WorldEvent
{
public int ID { get; set; }
public string Name { get; set; }
public List<Presentation> PresentationList { get; set; }
}
public class Presentation
{
public int ID { get; set; }
public string Name { get; set; }
public string Country { get; set; }
}
public class WorldEventService
{
public List<WorldEvent> GetWorldEvents()
{
List<WorldEvent> worldEventList = new List<WorldEvent>();
List<Presentation> presentationList = new List<Presentation>();
// Create list of Presentations for WorldEvent_1
presentationList = new List<Presentation>()
{
new Presentation() { ID = 1, Name = "Presentation_1", Country = "Germany",},
new Presentation() { ID = 2, Name = "Presentation_2", Country = "UK",},
new Presentation() { ID = 3, Name = "Presentation_3", Country = "UK",},
};
// Add WorldEvent_1 to the list of WorldEvents
worldEventList.Add(new WorldEvent()
{
ID = 1,
Name = "WorldEvent_1",
PresentationList = presentationList,
});
// Create list of Presentations for WorldEvent_2
presentationList = new List<Presentation>()
{
new Presentation() { ID = 4, Name = "Presentation_4", Country = "USA",},
new Presentation() { ID = 5, Name = "Presentation_5", Country = "UK",},
new Presentation() { ID = 6, Name = "Presentation_6", Country = "Japan",},
};
// Add WorldEvent_2 to the list of WorldEvents
worldEventList.Add(new WorldEvent()
{
ID = 2,
Name = "WorldEvent_2",
PresentationList = presentationList,
});
// Create list of Presentations for WorldEvent_3
presentationList = new List<Presentation>()
{
new Presentation() { ID = 7, Name = "Presentation_7", Country = "France",},
new Presentation() { ID = 8, Name = "Presentation_8", Country = "Germany",},
new Presentation() { ID = 9, Name = "Presentation_9", Country = "Japan",},
};
// Add WorldEvent_3 to the list of WorldEvents
worldEventList.Add(new WorldEvent()
{
ID = 3,
Name = "WorldEvent_3",
PresentationList = presentationList,
});
return worldEventList;
}
}
Now - how can I get a list of WorldEvents, whose Presentations took place in the UK.
And - in the list of my interest, WorldEvents should contain info about those UK Presentations only.
In other word, I need this as result:
WorldEvent_1(Presentation_2, Presentation_3)
WorldEvent_2(Presentation_5)
If I've understood what you want. There are many ways to do this, however you can filter first, then recreate your WorldEvents with the filtered list of Presentation
var country = "UK";
var result = worldEventList.Where(x => x.PresentationList.Any(y => y.Country == country))
.Select(x => new WorldEvent()
{
ID = x.ID,
Name = x.Name,
PresentationList = x.PresentationList
.Where(y => y.Country == country)
.ToList()
}).ToList();
or as noted by Gert Arnold in the comments you could filter after the fact
var result = worldEventList.Select(x => new WorldEvent()
{
ID = x.ID,
Name = x.Name,
PresentationList = x.PresentationList
.Where(y => y.Country == country).ToList()
}).Where(x => x.PresentationList.Any())
.ToList();
Note : Because this is not projecting (selecting) each Presentation, any change you make to a Presentation in the result will be reflected in the original data. If you don't want this, you will need to recreate each Presentation
var worldEvent = new WorldEventService.GetWorldEvents();
var filter = "";//userInput
var filteredResult = worldEvent.Select(r => new WorldEvent
{
PresentationList = r.PresentationList.Where(c => c.Country == filter).ToList(),
ID = r.Id,
Name = r.Name
}).ToList();
public static List<WorldEvent> Filter(string Country, List<WorldEvent> events) {
var evs = from ev in events.Where(x => x.PresentationList.Any(y => y.Country == Country))
let targetPres = from pres in ev.PresentationList
where pres.Country == Country
select pres
select new WorldEvent {
ID = ev.ID,
Name = ev.Name,
PresentationList = targetPres.ToList()
};
return evs.ToList();
}
Not sure if my understanding is correct, I guess there's a one to many relationship between your WorldEvent and Presentation table. So if you'd like to get all the WorldEvents and its related Presentations which take place in UK, by using EntityFramework, you can try this:
worldEventContext
.Include(PresentationContext)
.Select(
w => new
{
w.ID,
w.Name,
PresentationList = w.PresentationContext.Where(p => p.Country == "UK")
})
I have the following Linq query currently:
var clientsToPull =
parameters.Where(x => providers.Select(y => x.ClientGuid).Contains(x.ClientGuid))
.GroupBy(x => x.ClientGuid)
.Select(y => new ClientsToPull { ClientGuid = y.Key, StartDate = y.Min(c => c.FromDate)});
What this is meant to do is first check the parameters list to make sure that the client guid exists in the providers list. Then it groups the ClientGuids on the parameters list and selects the clientGuid and min StartDate.
The problem I have is that I need to also select some of the columns from the providers list such as a UserName. How exactly would I do this? Is it possible to do this in 1 linq statement, or would I have to do this in 2 separate statements?
public class Providers
{
public Guid ClientGuid {get; set;},
public string UserName {get; set;},
public string Password {get; set;}
}
public class Parameters
{
public Guid ClientGuid {get; set;},
public string StartDate {get; set;}
}
You can do it in one statement, using a Join to match your parameters with providers and project information from both. e.g...
var providers = new List<Providers>()
{
new Providers() { ClientGuid = Guid.NewGuid(), UserName = "A", Password = "B" },
new Providers() { ClientGuid = Guid.NewGuid(), UserName = "B", Password = "C" },
};
var parameters = new List<Parameters>()
{
new Parameters() { ClientGuid = providers.First().ClientGuid, StartDate = "C" },
new Parameters() { ClientGuid = providers.First().ClientGuid, StartDate = "B" },
new Parameters() { ClientGuid = providers.Take(2).Last().ClientGuid, StartDate = "Tomorrow!" },
new Parameters() { ClientGuid = Guid.NewGuid(), StartDate = "Last year" },
};
var result = parameters
.GroupBy(a => a.ClientGuid, a => a)
.Join(
providers,
parameterGroup => parameterGroup.Key,
provider => provider.ClientGuid,
(parameterGroup, provider) => new { provider.ClientGuid, provider.UserName, MinStartDate = parameterGroup.Min(groupMember => groupMember.StartDate) });
This may be a bit of a dumb question but I'm new to elastic search and nest.
I have a class
Person
{
public string Id {get; set;}
public string Name {get; set;}
public IList<string> PhoneNumbers {get; set;}
}
And what I want to do is update the Phone number by adding to it.
Right now I'm doing it with 2 queries but I'm wondering if there is a way I could manage it with 1.
// See if the Person already exists
var result = NestClient.Search<Person>(s => s
.Index(_indexName)
.Take(1)
.Query(q => q
.Term(p => p.Name, person.Name)
&& q.Term(p => p.Id, person.Id)));
if (result.ServerError != null)
{
throw result.OriginalException;
}
if (result.Documents.FirstOrDefault() == null)
{
var response = NestClient.Index<Person>(person);
if (response.ServerError != null)
{
throw response.OriginalException;
}
}
else
{
// If it does exist update and overwrite
var savedPerson = result.Documents.First();
IList<string> oldNums = SavedPerson.PhoneNumbers;
IList<string> newNums = newPerson.PhoneNumbers;
var combinedNums = oldNums.Concat(newNums);
newPerson.PhoneNumbers = combinedNums.ToList<string>();
var response = NestClient.Update(DocumentPath<Person>
.Id(newPerson.Id),
u => u.Doc(newPerson).DocAsUpsert(true));
if (response.ServerError != null)
{
throw response.OriginalException;
}
}
Basically I want my upsert to add to the existing list of phone numbers if it exists.
If script is an option you can do this with scripted update.
Option with duplicated items after update in array
var updateResponse = client.Update<Document, DocumentPartial>(DocumentPath<Document>.Id(1), descriptor => descriptor
.Script(#"ctx._source.array += tab;")
.Params(p => p.Add("tab", new[] {4, 5, 3})));
and without
var updateResponse = client.Update<Document, DocumentPartial>(DocumentPath<Document>.Id(1), descriptor => descriptor
.Script(#"ctx._source.array += tab; ctx._source.array.unique();")
.Params(p => p.Add("tab", new[] {4, 5, 3})));
Full example:
public class DocumentPartial
{
public int[] Array { get; set; }
}
public class Document
{
public int Id { get; set; }
public int[] Array { get; set; }
}
var client = new ElasticClient(settings);
client.CreateIndex(indexName, descriptor => descriptor
.Mappings(map => map
.Map<Document>(m => m.AutoMap())));
var items = new List<Document>
{
new Document
{
Id = 1,
Array = new[] {1,2,3}
}
};
var bulkResponse = client.IndexMany(items);
client.Refresh(indexName);
var updateResponse = client.Update<Document, DocumentPartial>(DocumentPath<Document>.Id(1), descriptor => descriptor
.Script(#"ctx._source.array += tab; ctx._source.array.unique();")
.Params(p => p.Add("tab", new[] {4, 5, 3})));
Hope it helps.
I need to present a list of Ads grouped by category, including the Ads Count for each category.
Categories are grouped by a Parent Category like Cars that include the Categories Saloon, Cabriolet and Sports.
Models:
public class Ad
{
[Key]
public int Id { get; set; }
public Category Category { get; set; }
}
public class Category
{
public int Id { get; set; }
[ForeignKey("CategoryParent")]
public int? CategoryParent_Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Ad> Ads { get; set; }
}
The result as to be:
Cars - Count: 100 (where 100 is the sum of for example 20 Saloon's Ads, 80 Cabrilet's)
At the moment, I'm only able to present the list of all Categories, and not grouped by Parent Category.
var adIds = {1,2,4,5}
var result =
from c in categoryQuery
let searchCount = c.Ads.Count(a => adIds.Contains(a.Id))
where searchCount > 0
select new CategoryGetAllBySearchDto
{
Id = c.CategoryParent_Id,
Name = c.CategoryParent.Name,
SearchCount = searchCount,
Ids = c.Ads.Where(a => adIds.Contains(a.Id)).Select(a => a.Id)
};
GroupBy in memory:
var adIds = { 1, 2, 4, 5 };
var result = categoryQuery.Where(c => c.Ads.Any(a => adIds.Contains(a.Id)))
.Select(c => new
{
c.CategoryParent_Id,
c.CategoryParent.Name,
Ids = c.Ads.Where(a => adIds.Contains(a.Id)).Select(a => a.Id).AsEnumerable()
})
.ToList()
.GroupBy(c => new {c.CategoryParent_Id, c.Name})
.Select(g => new CategoryGetAllBySearchDto
{
Id = g.Key.CategoryParent_Id,
Name = g.Key.Name,
Ids = g.SelectMany(u => u.Ids).AsEnumerable()
})
.ToList();
i think you need this:
var adIds = { 1, 2, 4, 5 };
var result = from c in categoryQuery
where c.Ads.Any(a => adIds.Contains(a.Id))
group c by new {c.CategoryParent_Id, c.CategoryParent.Name} into g
select new CategoryGetAllBySearchDto
{
Id = g.Key.CategoryParent_Id,
Name = g.Key.Name,
SearchCount = g.SelectMany(u => u.Ads)
.Where(a => adIds.Contains(a.Id))
.Count(),
Ids = g.SelectMany(u => u.Ads)
.Where(a => adIds.Contains(a.Id))
.Select(a => a.Id)
};
you can get out the SearchCount an add the AsEnumerable to Ids to get query just once
public class CategoryGetAllBySearchDto
{
public int? Id { get; set; }
public string Name { get; set; }
public int SearchCount { get { return this.Ids.Count() } }
public IEnumerable<int> Ids { get; set; }
}
and the query :
var adIds = { 1, 2, 4, 5 };
var result = from c in categoryQuery
where c.Ads.Any(a => adIds.Contains(a.Id))
group c by new {c.CategoryParent_Id, c.CategoryParent.Name} into g
select new CategoryGetAllBySearchDto
{
Id = g.Key.CategoryParent_Id,
Name = g.Key.Name,
Ids = g.SelectMany(u => u.Ads)
.Where(a => adIds.Contains(a.Id))
.Select(a => a.Id)
.AsEnumerable()
};