How do I group on one of two possible fields using LINQ? - c#

I am trying to get the latest contact with a given user, grouped by user:
public class ChatMessage
{
public string SentTo { get; set; }
public string SentFrom { get; set; }
public string MessageBody { get; set; }
public string SendDate { get; set; }
}
The user's contact info could either be in SentTo or SentFrom.
List<ChatMessage> ecml = new List<ChatMessage>();
var q = ecml.OrderByDescending(m => m.SendDate).First();
would give me the latest message, but I need the last message per user.
The closest solution I could find was LINQ Max Date with group by, but I cant seem to figure out the correct syntax. I would rather not create multiple List objects if I don't have to.
If the user's info is in SentTo, my info will be in SentFrom, and vice-versa, so I do have some way of checking where the user's data is.
Did I mention I was very new to LINQ? Any help would be greatly appreciated.

Since you need to interpret each record twice - i.e. as a SentTo and a SentFrom, the query becomes a bit tricky:
var res = ecml
.SelectMany(m => new[] {
new { User = m.SentFrom, m.SendDate }
, new { User = m.SentTo, m.SendDate }
})
.GroupBy(p => p.User)
.Select(g => new {
User = g.Key
, Last = g.OrderByDescending(m => m.SendDate).First()
});
The key trick is in SelectMany, which makes each ChatMessage item into two anonymous items - one that pairs up the SentFrom user with SendDate, and one that pairs up the SentTo user with the same date.
Once you have both records in an enumerable, the rest is straightforward: you group by the user, and then apply the query from your post to each group.

It should be pretty easy, look at this code:
string username = "John";
var q = ecml.Where(i=>i.SentFrom == username || i.SentTo == username).OrderByDescending(m => m.SendDate).First();
It simply filter your collection be choosing items which either SentFrom or SentTo is equal to username.

Related

Convert Many-to-Many relationship to single class with ADO.Net

I have ViewModel
UserGroup.cs
public class UserGroup
{
public User User { get; set; }
public List<Group> Groups { get; set; }
}
And I want to have the User and all related Groups. The problem is that the query returns duplicates of all users if there is more than 1 group related to him (and It's expected of course). The first thing that comes to my mind is to fetch all the users with single query and then foreach user to get the groups related to him and push it in the list. But I'm looking for a better way (if there is) to do that.
The relationship is made with junction table.
SQL Query
SELECT u.UserName, ug.GroupName FROM auth.UserUserGroup uug
INNER JOIN [auth].[User] u ON u.UserId = uug.UserId
INNER JOIN auth.UserGroup ug ON ug.UserGroupId = uug.UserGroupId
I'm sure someone will have a more elegant solution, but I've used Linq to construct a tiered object a number of times
var dbRecords = repository.Get(userId); // However you are getting your records, do that here
var result = dbRecords.GroupBy(x => x.UserId)
.Select(g => new UserGroup
{
User = new User { UserName = g.First().UserName },
Groups = g.Select(y => new Group { GroupName = y.GroupName }
}
);
I've tried to use your object names. Hopefully I didn't make any mistakes

Left join with linq

I have a Activities model as follows
public class Activity
{
// this is email
public string CreatedBy {set;get;}
// Relationship
public ApplicationUser User{set;get;}
}
Then I have the User model:
public class ApplicationUser
{
// ID from Identity
public string Id{set;get;}
public string Email {set;get;}
}
Of course I have the corresponding tables in the database.
What i need to find out is the users who didnt do any activity.
While the code below works, it is not efficient and times out. Because I have 500K activity in the Activities table.
var userz = _db.Users.AsNoTracking();
var groupedUsers = _db.Activities.AsNoTracking().GroupBy(x => x.CreatedBy).Select(group => new { CreatedBy = group.Key, Count = 1 }).Select(x=> x.CreatedBy);
var result = userz.Where(x => groupedUsers.Contains(x.Email) == false);
I tried the same Query for Navigation property, which is indexed, ie: User above. Yet the query times out.
Is there a more efficient solution for this using left join?
You should be better of with foreign keys but if this is really how your classes look you could try
_db.Users.Where(u => !_db.Activities.Any(u => a.ApplicationUser == u));

Displaying related data from the same table

I think I'm having a senior moment, but I also think I have not run into this situation before. I have two columns in my MVC5 Identity 2.1 Users table.
UserId | BannedBy (and also an IsBanned bool)
Both fields are userid guid strings. However, BannedBy refers to a different user in the same Users table.
When I display my view of banned users (a table and each row is one banned user), I don't want to show the BannedBy guid, I want to show the related UserName for that BannedBy guid. However, I can't seem to figure out what I need to do.
I've tried a ViewModel and method approach:
public ActionResult BannedUsers()
{
var bannedUsers = db.Users.Where(d => d.IsBanned);
var model = new BannedUsersViewModel
{
BannedUsers = bannedUsers,
BannedByUserName = GetUserName(bannedUsers.BannedBy)
};
return View(model);
}
Then like an outer approach to my viewmodel:
var model = new BannedUsersViewModel
{
BannedUsers = bannedUsers
};
model.BannedByUserName = GetUserName(model.bannedUsers.BannedBy);
However, it seems I can't use the bannedUsers.BannedBy (I also tried all that above with a capital B... BannedUsers.BannedBy) data before it's actually been rendered? And now I've scrapped the viewmodel and am trying to do like a related data join on my query:
db.Users.Join(d => d.BannedBy == d.UserId).Where(d => d.IsBanned);
(I'm sure this is way off, I'm just trying to give you an idea)
Does anyone know the proper way of doing this? I was also thinking about calling a method from my view, but seems like that would be breaking the MVC rules?
Thank you.
Update: Here is the GetUserName method:
public string GetUserName(string userId)
{
var result = db.Users.Find(userId);
return result.UserName;
}
Update #2: Here is the BannedUsersViewModel:
public class BannedUsersViewModel
{
public IEnumerable<ApplicationUser> BannedUsers { get; set; }
public string BannedByUserName { get; set; }
}
Update #3: A pic:
I am going to take a stab at this. We can modify it as needed (unless I am completely off base, in which case, I will delete this and we will all pretend it never happened). Does this get you in the ballpark:
public ActionResult BannedUsers()
{
var bannedUsers =
db.Users
.Where(d => d.IsBanned)
.Join(
db.Users,
bannee => bannee.BannedBy,
banner => banner.UserId,
(bannee, banner) => new BannedUser()
{
BannedByUserName = banner.UserName,
BannedUser = bannee
})
.AsEnumerable();
var model = new BannedUsersViewModel
{
BannedUsers = bannedUsers
};
return View(model);
}
public class BannedUsersViewModel
{
public IEnumerable<BannedUser> BannedUsers { get; set; }
}
public class BannedUser
{
public ApplicationUser BannedUser { get; set; }
public string BannedByUserName { get; set; }
}
The idea is that we get all of the banned users, join them to all of the users that banned those users and then group by the user that banned them. You end up with a collection of objects that have the user that banned other users and the users they banned.
So far, I went with #lazy's comment in doing a self join. I'm not sure how good I feel about it though. So essentially, I added this to my IdentityModels.cs under public class ApplicationUser : IdentityUser:
[ForeignKey("BannedBy")]
public virtual ApplicationUser BannedByUser { get; set; }
Then in my controller, changed my original query to a list:
var bannedUsers = db.Users.Where(d => d.IsBanned).ToList();
return View(bannedUsers);
(If I don't convert to list, it complains about having more than one data reader open.) Then in my View in my foreach loop:
#Html.DisplayFor(modelItem => item.BannedByUser.UserName)
And boom:
I'm a little worried of the performance impact?...especially for a page that isn't used that often and is not really that important. Is there an impact if the page isn't being called (like with the Index that was created and such)? I'm also a little leery since there seems to be some magic happening with Identity.
Anyway, I'm still open to other ideas...or thoughts about this one. Thanks again for everyone's help.

Windows Azure Mobile Service query table

I use Windows Azure Mobile Service.
I have a table of Element.
I want to query the cloud database :
select Id, Name
FROM Element ORDER BY creationTime
But I don't understand at all the "query" system with Windows Azure Mobile Service.
I have a IMobileServiceTable but don't know what to do with that...
I checked on tutorial, and they explain how to use Where clause, but not select. And I need to select only some column because my element have picture and I don't want to download it in my getAll method....
Edit :
I try that :
Task.Factory.StartNew(() =>
{
var query = table.Select(x =>
new Element()
{
Id = x.Id,
Name = x.Name,
Price = x.Price
});
var _items = query.ToListAsync().Result;
}).ContinueWith((x) => handleProductsArrived(x.Result));
But it doesn't work.
You can find a helpful post from Carlos that includes what the corresponding SQL query would be here: http://blogs.msdn.com/b/carlosfigueira/archive/2012/09/21/playing-with-the-query-object-in-read-operations-on-azure-mobile-services.aspx
For example:
function read(query, user, request) {
query.where({ UserId: user.userId })
.select('id', 'MovieName', 'MovieRating')
.orderBy('MovieName')
.take(10);
request.execute();
}
woudld translate to
SELECT TOP 10 [id], [MovieName], [MovieRating]
FROM MovieRating
WHERE Rating > 2 AND UserId = ?
ORDER BY MovieName
So for your case where you need to translate
SELECT Id, Name
FROM Element
ORDER BY creationTime
you'd go with something like the following:
function read(query, user, request) {
query.where({ UserId: user.userId })
.select('id', 'Name', 'Element')
.orderBy('creationTime')
request.execute();
}
It sounds like you are just looking to do a simple query with IMobileServiceTable
SELECT Id, Name FROM Element ORDER BY creationTime
If you do not mind using the IMobileServiceTable<TodoItem>, you can try:
1) Removing the member properties you do not need from your Object
Example:
public class TodoItem
{
public int Id { get; set; }
// REMOVE WHAT YOU DO NOT WANT
//[DataMember(Name = "text")]
//public string Text { get; set; }
[DataMember(Name = "complete")]
public bool Complete { get; set; }
}
2) Here's the code to read the data:
private void RefreshTodoItems()
{
items = todoTable
.OrderBy( todoItem => todoItem.Id )
.Take(10)
.ToCollectionView();
ListItems.ItemsSource = items;
}
which is basically:
SELECT TOP 10 Id, Complete FROM TodoTable ORDER BY Id
The code example for todoTable is at http://www.windowsazure.com/en-us/develop/mobile/tutorials/get-started-wp8/
Hope this helps.
If you're using .net, you pretty much follow linq.
Looking at the sample app - where it has -
private void RefreshTodoItems()
{
// This code refreshes the entries in the list view be querying the TodoItems table.
// The query excludes completed TodoItems
items = todoTable
.Where(todoItem => todoItem.Complete == false)
.ToCollectionView();
ListItems.ItemsSource = items;
}
If, for example, you did not want to return the Complete flag you could add before the call to .ToCollectionView()
.Select(item=>new {item.Id, item.Text})
Which would create a list of a new object of anonymous type (can be a concrete type) with the two members specified.

RavenDB index for nested query

I'm pretty new to RavenDB and am struggling to find a solution to the following:
I have a collection called ServiceCalls that look like this:
public class ServiceCall
{
public int ID { get; set; }
public string IncidentNumber { get; set; }
public string Category { get; set; }
public string SubCategory { get; set; }
public DateTime ReportedDateTime { get; set; }
public string Block { get; set; }
public decimal Latitude { get; set; }
public decimal Longitude { get; set; }
}
I have an index named ServiceCalls/CallsByCategory that looks like this:
Map = docs => from doc in docs
select new
{
Category = doc.Category,
CategoryCount = 1,
ServiceCalls = doc,
};
Reduce = results => from result in results
group result by result.Category into g
select new
{
Category = g.Key,
CategoryCount = g.Count(),
ServiceCalls = g.Select(i => i.ServiceCalls)
};
So the output is:
public class ServiceCallsByCategory
{
public string Category { get; set; }
public int CategoryCount { get; set; }
public IEnumerable<ServiceCall> ServiceCalls { get; set; }
}
using this query everything works as it should
var q = from i in session.Query<ServiceCallsByCategory>("ServiceCalls/CallsByCategory") select i
Where I am absolutely lost is writing an index that would allow me to query by ReportedDateTime. Something that would allow me to do this:
var q = from i in session.Query<ServiceCallsByCategory>("ServiceCalls/CallsByCategory")
where i.ServiceCalls.Any(x=>x.ReportedDateTime >= new DateTime(2012,10,1))
select i
Any guidance would be MUCH appreciated.
A few things,
You can't have a .Count() method in your reduce clause. If you look closely, you will find your counts are wrong. As of build 2151, this will actually throw an exception. Instead, you want CategoryCount = g.Sum(x => x.CategoryCount)
You always want the structure of the map to match the structure of the reduce. If you're going to build a list of things, then you should map a single element array of each thing, and use .SelectMany() in the reduce step. The way you have it now only works due to a quirk that will probably be fixed at some point.
By building the result as a list of ServiceCalls, you are copying the entire document into the index storage. Not only is that inefficient, but it's unnecessary. You would do better keeping a list of just the ids. Raven has an .Include() method that you can use if you need to retrieve the full document. The main advantage here is that you are guaranteed to have the most current data for each item you get back, even if your index results are still stale.
Putting all three together, the correct index would be:
public class ServiceCallsByCategory
{
public string Category { get; set; }
public int CategoryCount { get; set; }
public int[] ServiceCallIds { get; set; }
}
public class ServiceCalls_CallsByCategory : AbstractIndexCreationTask<ServiceCall, ServiceCallsByCategory>
{
public ServiceCalls_CallsByCategory()
{
Map = docs => from doc in docs
select new {
Category = doc.Category,
CategoryCount = 1,
ServiceCallIds = new[] { doc.ID },
};
Reduce = results => from result in results
group result by result.Category
into g
select new {
Category = g.Key,
CategoryCount = g.Sum(x => x.CategoryCount),
ServiceCallIds = g.SelectMany(i => i.ServiceCallIds)
};
}
}
Querying it with includes, would look like this:
var q = session.Query<ServiceCallsByCategory, ServiceCalls_CallsByCategory>()
.Include<ServiceCallsByCategory, ServiceCall>(x => x.ServiceCallIds);
When you need a document, you still load it with session.Load<ServiceCall>(id) but Raven will not have to make a round trip back to the server to get it.
NOW - that doesn't address your question about how to filter the results by date. For that, you really need to think about what you are trying to accomplish. All of the above would assume that you really want every service call shown for each category at once. Most of the time, that's not going to be practical because you want to paginate results. You probably DON'T want to even use what I've described above. I am making some grand assumptions here, but most of the time one would filter by category, not group by it.
Let's say you had an index that just counts the categories (the above index without the list of service calls). You might use that to display an overview screen. But you wouldn't be interested in the documents that were in each category until you clicked one and drilled into a details screen. At that point, you know which category you're in, and you can filter by it and reduce to a date range without a static index:
var q = session.Query<ServiceCall>().Where(x=> x.Category == category && x.ReportedDateTime >= datetime)
If I am wrong and you really DO need to show all documents from all categories, grouped by category, and filtered by date, then you are going to have to adopt an advanced technique like the one I described in this other StackOverflow answer. If this is really what you need, let me know in comments and I'll see if i can write it for you. You will need Raven 2.0 to make it work.
Also - be very careful about what you are storing for ReportedDateTime. If you are going to be doing any comparisons at all, you need to understand the difference between calendar time and instantaneous time. Calendar time has quirks like daylight savings transitions, time zone differences, and more. Instantaneous time tracks the moment something happened, regardless of who's asking. You probably want instantaneous time for your usage, which means either using a UTC DateTime, or switching to DateTimeOffset which will let you represent instantaneous time without losing the local contextual value.
Update
I experimented with trying to build an index that would use that technique I described to let you have all results in your category groups but still filter by date. Unfortunately, it's just not possible. You would have to have all ServiceCalls grouped together in the original document and express it in the Map. It doesn't work the same way at all if you have to Reduce first. So you really should just consider simple query for ServiceCalls once you are in a specific Category.
Could you add ReportedDateTime to the Map and aggregate it in the Reduce? If you only care about the max per category, something like this should be sufficient.
Map = docs => from doc in docs
select new
{
Category = doc.Category,
CategoryCount = 1,
ServiceCalls = doc,
ReportedDateTime
};
Reduce = results => from result in results
group result by result.Category into g
select new
{
Category = g.Key,
CategoryCount = g.Sum(x => x.CategoryCount),
ServiceCalls = g.Select(i => i.ServiceCalls)
ReportedDateTime = g.Max(rdt => rdt.ReportedDateTime)
};
You could then query it just based on the aggregated ReportedDateTime:
var q = from i in session.Query<ServiceCallsByCategory>("ServiceCalls/CallsByCategory")
where i.ReportedDateTime >= new DateTime(2012,10,1)
select i

Categories