How can I merge duplicate objects with Linq while concatenating a property? - c#

I have an object:
private class User
{
public string fullname { get; set; }
public string manager { get; set; }
public string businessunit { get; set; }
public string employeeid { get; set; }
public string mainphone { get; set; }
public string fax { get; set; }
public string mobile { get; set; }
public string email { get; set; }
public string street3 { get; set; }
public string street2 { get; set; }
public string street1 { get; set; }
public string city { get; set; }
public string stateorprovince { get; set; }
public string ziporpostalcode { get; set; }
public string department { get; set; }
public string countryorregion { get; set; }
public string salesarea { get; set; }
public string title { get; set; }
public string username { get; set; }
public string companyname { get; set; }
public string securityroles { get; set; }
public User()
{
}
}
I also have a list of that object:
List<User> users = new List<User>();
What I'd like to do is merge all Users in the list with the same fullname while concatenating all of their different securityroles into the same securityroles property.
I was thinking something along the lines of this would work:
users = (from user in users
group user by user.fullname
/*not sure what to put here*/).ToList();

I would write a function that merges a collection of Users into one and call that from your query:
private User Merge(IEnumerable<User> users)
{
User user = new User();
if(!users.Any()) return user; // or null, whatever you think is appropriate
user.fullname = users.First().fullname;
user.securityroles = string.Join(", ", users.Select(u => u.securityroles));
// similar rules/queries for other properties.
}
Then just call it from the main query:
users = (from user in users
group user by user.fullname into g
select Merge(g)
).ToList();
If you don't like having multiple queries in the Merge function, then re-write it as a foreach loop, or use whatever construct you see fit.
The point is to get something that works and defines your rules for "merge" outside of your Linq query, then make it better.

Using D Stanley answer, I ran the code on online compiler, with some mods. Needs to add the others properties in merge function, like Stanley said.
http://rextester.com/YJV50083
The user list (the 'A,B,C,D,E' are securityroles):
Users:
Test1 A
Test1 B
Test2 C
Test2 D
Test3 E
After linq:
New users:
Test1 A, B
Test2 C, D
Test3 E

Thank you D Stanley and mzaverez. This function works but I might change it since the merge function executes multiple queries:
private User Merge(IEnumerable<User> users)
{
User u = new User();
if (!users.Any())
{
return u;
}
else
{
u.fullname = users.First().fullname;
u.manager = users.First().manager;
u.businessunit = users.First().businessunit;
u.employeeid = users.First().employeeid;
u.mainphone = users.First().mainphone;
u.fax = users.First().fax;
u.mobile = users.First().mobile;
u.email = users.First().email;
u.street1 = users.First().street1;
u.street2 = users.First().street2;
u.street3 = users.First().street3;
u.city = users.First().city;
u.stateorprovince = users.First().stateorprovince;
u.ziporpostalcode = users.First().ziporpostalcode;
u.department = users.First().department;
u.countryorregion = users.First().countryorregion;
u.salesarea = users.First().salesarea;
u.title = users.First().title;
u.username = users.First().username;
u.companyname = users.First().companyname;
u.securityroles = string.Join(", ", users.Select(x => x.securityroles));
return u;
}
}
That function is called by the very helpful Linq code D Stanley suggested below:
users = (from user in users
group user by user.fullname into g
select Merge(g)
).ToList();

Related

C# Model Query returns no result because Joins may not have records

I have a query and I cannot remember. I guess my real question is, Do I need the extra Joins in this query and if yes them how do I handle the fact that Users may not have Posts, Tasks, or Notifications. I know that you can use coalesce but in this instance it doesn't appear to like it. So How do I get a successful Query if no records are found in the tables of the joins.
In the controller I do a redirect
if (modelInstance == null)
{
return RedirectToAction("Index", "Dashboard");
}
If I remove the joins it will populate the View Page. My guess is because there are no records in these joins - Yet..
Here is my Query:
public class ProfileViewModel
{
public static ProfileViewModel GetUserProfile(string id, GeneralEntities db)
{
var qUser = from usr in db.Users
join post in db.Posts on usr.AspNetUsersId equals post.CreatedBy
join task in db.Tasks on usr.AspNetUsersId equals task.UsersId
join notif in db.Notifications on usr.AspNetUsersId equals notif.UsersId
where (usr.AspNetUsersId == id)
select new ProfileViewModel
{
AspNetUsersId = usr.AspNetUsersId,
FirstName = usr.FirstName,
LastName = usr.LastName,
ProfileImage = usr.ProfileImage,
Title = usr.Title,
Education = usr.Education,
Skills = usr.Skills,
About = usr.About,
IsFemale = usr.IsFemale,
IsMale = usr.IsMale,
IsStaff = usr.IsStaff
};
var result = qUser.FirstOrDefault();
if (result != null)
{
result.PostResults = db.Posts.Where(x => x.CreatedBy == result.AspNetUsersId);
result.TaskResults = db.Tasks.Where(x => x.UsersId == result.AspNetUsersId);
result.NotificationResults = db.Notifications.Where(x => x.UsersId == result.AspNetUsersId);
};
return result;
}
public string UsersId { get; set; }
public string AspNetUsersId { get; set; }
[Display(Name = "F Name")]
public string FirstName { get; set; }
[Display(Name = "L Name")]
public string LastName { get; set; }
[Display(Name = "Profile Image")]
public string ProfileImage { get; set; }
[Display(Name = "Job Title")]
public string Title { get; set; }
[Display(Name = "Education")]
public string Education { get; set; }
[Display(Name = "Skills")]
public string Skills { get; set; }
[Display(Name = "About")]
public string About { get; set; }
public bool? IsStaff { get; set; }
public bool? IsMale { get; set; }
public bool? IsFemale { get; set; }
public virtual IEnumerable<Post> PostResults { get; set; }
public virtual IEnumerable<Tasks> TaskResults { get; set; }
public virtual IEnumerable<Notification> NotificationResults { get; set; }
}
I also tried the .Where(x => x.UserId != null) but that did not work either.
thanks for your help!
While you can use LEFT JOIN to handle the case where the joined table can have zero-to-many results, the result isn't going to be what you appear to need based on the structure of your ProfileViewModel class.
If you always want to return the matching user then a simple query on db.Users to get the record is sufficient. You'll end up with 4 queries - one for the user and one each for the related records - but they'll all be quite straight-forward:
var usr = db.Users.FirstOrDefault(u => u.AspNetUsersId == id);
if (usr == null)
return null;
return new ProfileViewModel
{
AspNetUsersId = usr.AspNetUsersId,
// ...the rest of the usr fields...
PostResults = db.Posts.Where(x => x.CreatedBy == id),
// ...and so on.
};
Note that since IQueryable<> implements IEnumerable<> then each time you enumerate over PostResults, TaskResults or NotificationResults you'll be running a query against the database.
Alternatively you could include some extras in your usr query to get metrics on the linked tables - number of items for instance, or just a flag saying whether or not they have any linked rows:
var usr =
(
from usr in db.Users
where usr.AspNetUsersId == id
select new
{
usr,
postCount = db.Posts.Count(p => p.CreatedBy == usr.AspNetUsersId),
hasTasks = db.Tasks.Any(t => t.UsersId == usr.AspNetUsersId),
hasNotes = db.Notifications.Any(n => n.UsersId == usr.AspNetUsersId),
}
).FirstOrDefault();
That could be useful for reducing the query load later. If there are no posts for instance then assigning an empty array to the PostResults field is a good idea. Maybe you want to load all of the posts if there are only a handful rather than have them queried multiple times later.
A note since this is ASP.NET: You want to be very careful about those Results fields.
Deferred queries are useful, but when they outlive their data context they can cause problems. In a simple MVC view model context you're probably fine, just as long as you understand the risks. For short-term 'fetch and display' this should be perfectly fine, but if the model is stored and reused anywhere then don't save an IQueryable<>. Materialize your queries with ToArray() or similar in those instances, or explicitly recreate the query against the current data context when you need it.
Here is the working code with the Left joins that #YongShun led me to. I am still wondering if I really need these joins in my current query, Nevertheless if someone can find this useful here it is..
public class ProfileViewModel
{
public static ProfileViewModel GetUserProfile(string id, GeneralEntities db)
{
var qUser = from usr in db.Users
join post in db.Posts on usr.AspNetUsersId equals post.CreatedBy into a from post in a.DefaultIfEmpty()
join task in db.Tasks on usr.AspNetUsersId equals task.UsersId into b from task in b.DefaultIfEmpty()
join notif in db.Notifications on usr.AspNetUsersId equals notif.UsersId into c from notif in c.DefaultIfEmpty()
where (usr.AspNetUsersId == id)
select new ProfileViewModel
{
AspNetUsersId = usr.AspNetUsersId,
FirstName = usr.FirstName,
LastName = usr.LastName,
ProfileImage = usr.ProfileImage,
Title = usr.Title,
Education = usr.Education,
Skills = usr.Skills,
About = usr.About,
IsFemale = usr.IsFemale,
IsMale = usr.IsMale,
IsStaff = usr.IsStaff
};
var result = qUser.FirstOrDefault();
if (result != null)
{
result.PostResults = db.Posts.Where(x => x.CreatedBy == result.AspNetUsersId);
result.TaskResults = db.Tasks.Where(x => x.UsersId == result.AspNetUsersId);
result.NotificationResults = db.Notifications.Where(x => x.UsersId == result.AspNetUsersId);
};
return result;
}
public string UsersId { get; set; }
public string AspNetUsersId { get; set; }
[Display(Name = "F Name")]
public string FirstName { get; set; }
[Display(Name = "L Name")]
public string LastName { get; set; }
[Display(Name = "Profile Image")]
public string ProfileImage { get; set; }
[Display(Name = "Job Title")]
public string Title { get; set; }
[Display(Name = "Education")]
public string Education { get; set; }
[Display(Name = "Skills")]
public string Skills { get; set; }
[Display(Name = "About")]
public string About { get; set; }
public bool? IsStaff { get; set; }
public bool? IsMale { get; set; }
public bool? IsFemale { get; set; }
public virtual IEnumerable<Post> PostResults { get; set; }
public virtual IEnumerable<Tasks> TaskResults { get; set; }
public virtual IEnumerable<Notification> NotificationResults { get; set; }
}

Linq to SQL - Get Related Data

I've got some data that I need to return some of its related data and put it all in a model. I have all the appropriate fields setup in my model, and looks like this:
public class ComputerModel
{
public int MachineId { get; set; }
public int GroupId { get; set; }
public int SoftwareVersionId { get; set; }
public string GroupName { get; set; }
public string SoftwareVersion { get; set; }
public string IPAddress { get; set; }
public string HostName { get; set; }
public string MACAddress { get; set; }
public string Title { get; set; }
public bool IsIGMonitor { get; set; }
public string UpTime { get; set; }
public DateTime DateEntered { get; set; }
public string EnteredBy { get; set; }
public Nullable<DateTime> DateUpdated { get; set; }
public string UpdatedBy { get; set; }
public ICollection<MachineRole> MachineRoles { get; set; }
public ICollection<Role> Roles { get; set; }
}
Here's the linq statement I'm trying to use:
var query = (from m in unitOfWork.Context.Machines
join u in unitOfWork.Context.Users
on m.EnteredBy equals u.UserId into EntByUser
from EnteredByUser in EntByUser.DefaultIfEmpty()
join u2 in unitOfWork.Context.Users
on m.UpdatedBy equals u2.UserId into UpdByUser
from UpdatedByUser in UpdByUser.DefaultIfEmpty()
join g in unitOfWork.Context.Groups
on m.GroupId equals g.GroupId into Grp
from Groups in Grp.DefaultIfEmpty()
join s in unitOfWork.Context.SoftwareVersions
on m.SoftwareVersionId equals s.SoftwareVersionId into SW
from SoftwareVersions in SW.DefaultIfEmpty()
join mr in unitOfWork.Context.MachineRoles
on m.MachineId equals mr.MachineId into MachRoles
from MachineRoles in MachRoles.DefaultIfEmpty()
join r in unitOfWork.Context.Roles
on MachineRoles.RoleId equals r.RoleId into Rolz
from Rolz2 in Rolz.DefaultIfEmpty()
select new ComputerModel()
{
MachineId = m.MachineId,
GroupId = m.GroupId,
SoftwareVersionId = m.SoftwareVersionId,
GroupName = Groups.GroupName,
SoftwareVersion = SoftwareVersions.Version,
IPAddress = m.IPAddress,
HostName = m.HostName,
MACAddress = m.MACAddress,
Title = m.Title,
IsIGMonitor = m.IsIGMonitor,
UpTime = m.UpTime,
DateEntered = m.DateEntered,
DateUpdated = m.DateUpdated,
EnteredBy = EnteredByUser.FirstName + " " + EnteredByUser.LastName,
UpdatedBy = UpdatedByUser.FirstName + " " + UpdatedByUser.LastName,
MachineRoles = m.MachineRoles,
Roles = ?????
}).ToList();
I can get MachineRoles to populate but I cannot get Roles to populate. I've tried Roles = Rolz2 but Rolz returns a single instance of Role, not a collection.
How can I get this query to return Machines and the related data for both MachineRoles and Roles?
I've looked at the following articles but haven't had much luck:
This SO Article
Loading Related Data - MSDN
Using Include with Entity Framework
UPDATE
I notice if I remove my model and use an anonymous type, then I don't get any errors:
select new ()
{
GroupName = Groups.GroupName,
......
}).ToList();
But this doesn't help me in my project because I need to use a Model for the data.
If all the above tables are related via PK-FK relationship you can use linq lambda functions .Include() to include related tables and then use Navigation properties to access data.
If the tables are not related you can use LINQ left joins as shown in http://www.devcurry.com/2011/01/linq-left-join-example-in-c.html.
It looks like you need a mix of inner and left joins. The above example and only achieve inner joins.

Entity Framework to json - grouping data

I have a class named Client which looks like this:
public class Client
{
[Key, ForeignKey("BaseAssignments")]
public int ClientId { get; set; }
public string Owner { get; set; }
public string CompanyName { get; set; }
public virtual ICollection<BaseAssignment> BaseAssignments { get; set; }
}
And a class named Base looking like this:
public class Base
{
[Key, ForeignKey("BaseAssignments")]
public int BaseId { get; set; }
public string BaseName { get; set; }
public DateTime BaseStart { get; set; }
public DateTime BaseEnd { get; set; }
public virtual ICollection<BaseAssignment> BaseAssignments { get; set; }
}
They are to be joined with another class called BaseAssignment:
public class BaseAssignment
{
[Key]
public int BaseAssignmentId { get; set; }
public int BaseId { get; set; }
public int ClientId { get; set; }
public virtual Base Base { get; set; }
public virtual Client Client { get; set; }
}
The idea is that a client can be assigned to many bases, and one base can contain many clients.
Moving forward, I am trying to serialize base entitites in such way that a json representation of a base would have a collection of all it's clients as a subobject. A Web Api method that I'm trying to achieve this is:
db.Configuration.ProxyCreationEnabled = false;
var query = from b in db.Bases
group b by b.BaseId into nb
join ba in db.BaseAssignments on nb.FirstOrDefault().BaseId equals ba.BaseId
join c in db.Clients on ba.ClientId equals c.ClientId
select new BaseDTO
{
BaseName = nb.FirstOrDefault().BaseName,
BaseStart = nb.FirstOrDefault().BaseStart,
BaseEnd = nb.FirstOrDefault().BaseEnd,
Clients = from c1 in db.Clients select new ClientDTO
{
ClientId = c1.ClientId,
CompanyName = c1.CompanyName,
Owner = c1.Owner
}
};
return query;
where a BaseDTO looks like:
public class BaseDTO
{
public String BaseName { get; set; }
public DateTime BaseStart { get; set; }
public DateTime BaseEnd { get; set; }
public IQueryable<ClientDTO> Clients { get; set; }
}
and ClientDTO looks like:
public class ClientDTO
{
public int ClientId { get; set; }
public string Owner { get; set; }
public string CompanyName { get; set; }
}
As of now I'm getting an error stating that ClientDTO is an unexpected type. What can I do to fix this, or maybe the way that I've chosen is completely wrong? Thanks in advance for any insight on this.
EDIT
I've made some changes to the Web Api controller method, so it looks like:
db.Configuration.ProxyCreationEnabled = false;
var query = from b in db.Bases
group b by b.BaseId into nb
join ba in db.BaseAssignments on nb.FirstOrDefault().BaseId equals ba.BaseId
join c in db.Clients on ba.ClientId equals c.ClientId
select new BaseDTO
{
BaseName = nb.FirstOrDefault().BaseName,
BaseStart = nb.FirstOrDefault().BaseStart,
BaseEnd = nb.FirstOrDefault().BaseEnd,
Clients = new ClientDTO
{
ClientId = c.ClientId,
CompanyName = c.CompanyName,
Owner = c.Owner
}
};
return query;
This makes the Api produce a JSON, but it still contains a single object for every client, not every base.
You shouldn't have to group or join anything by hand for this, just use a sub-select and have LINQ to the heavy lifting.
from b in db.Bases
select new BaseDTO
{
BaseName = b.BaseName,
BaseStart = b.BaseStart,
BaseEnd = b.BaseEnd,
Clients =
from ba in b.BaseAssignments
from c in ba.Client
select new ClientDTO
{
ClientId = c.ClientId,
CompanyName = c.CompanyName,
Owner = c.Owner
}
}
Based on StriplingWarrior's suggestion, mixing best of both worlds into:
from b in db.Bases
select new BaseDTO
{
BaseName = b.BaseName,
BaseStart = b.BaseStart,
BaseEnd = b.BaseEnd,
Clients =
from ba in b.BaseAssignments
join c in db.Clients on ba.ClientId equals c.ClientId
select new ClientDTO
{
ClientId = c.ClientId,
CompanyName = c.CompanyName,
Owner = c.Owner
}
};
got the JSON I wanted - thanks.

Use multiple conditions in join LINQ. i,e AND

How to use multiple condition in LINQ joins, i.e. in my scenario I need to get all the users from table User where group ID = 4 from table UserInGroup, where UserInGroup is intermediate table between User and Group table as in SQL-T we use join as
select *
from user
where user.userID = userIngroup.userID AND userIngroup.groupID == 4
....
In another approach I am using lambda expression along with LINQ, how I can apply where groupID = 4 in following one??
public IEnumerable<User> GetUsersByGroupID(int _groupID)
{
List<User> _listedUsersByGroupID = new List<User>();
using(var _uow = new UserManagement_UnitOfWork())
{
_listedUsersByGroupID = (from _users in _uow.User_Repository.GetAll()
.Include(s=>s.UserInGroup.Select(r=>r.Group))
select _users).ToList();
return _listedUsersByGroupID;
}
}
User Model
[Table("User")]
public class User
{
public User() { }
[Key]
public int UserID { get; set; }
[StringLength(250)]
[Required]
public string FirstName { get; set; }
[StringLength(250)]
[Required]
public string LastName { get; set; }
[Required]
public int Age { get; set; }
[StringLength(250)]
[Required]
public string EmailAddress { get; set; }
public ICollection<UserInGroup> UserInGroup { get; set; }
}
UserInGroup Model
[Table("UserInGroup")]
public class UserInGroup
{
public UserInGroup() { }
[Key]
public int UserGroupID { get; set; }
[Required]
public int UserID { get; set; }
[Required]
public int GroupID { get; set; }
public User User { get; set; }
public Group Group { get; set; }
}
Group Model
public class Group
{
public Group() { }
[Key]
public int GroupID { get; set; }
[StringLength(250)]
[Required]
public string GroupName { get; set; }
public ICollection<UserInGroup> UserInGroup { get; set; }
}
You only need to add a condition to filter the users that belong to the group 4. Try this:
_listedUsersByGroupID = (from _user in _uow.User_Repository.GetAll()
.Include(s=>s.UserInGroup.Select(r=>r.Group))
where user.UserInGroup.Any(ug=>ug.groupID==4)
select _user).ToList();
Lambda query would look something like:
ctx.User.Where(user=>
ctx.UserInGroup.Any(userIngroup=>
user.userID == userIngroup.userID && userIngroup.groupID == 4
)
)
That however is just query, if you want to get results add .AsList() or .AsEnumerable() to end.
However you can write silly and inefficient code if you do not fully understand what you are doing. I would reccomend you try this instead:
var publications = ctx.Database.SqlQuery<UserResults>(String.Format(#"
select UserID, FirstName,LastName,Age,EmailAddress,UserInGroup
from user
where user.userID = userIngroup.userID AND userIngroup.groupID == {0}
order by UserID
", Config.Group));
Where Config.Group is 4; UserResults can be User table as well, if you do not want other fields. You need to execute or enumerate over the sql query to use the data and like before you can use .AsList() or .AsEnumerable() for that.
Variable ctx is database context. For example:
using (var ctx = new toxicEntities())
{
}

LINQ to Entities Special Database Query not working

I have a database with two tables (UserData) and (UserDetails). I use the LINQtoEntities.
The issue here is that in the LINQ query is not accepted:
Details = c.ToList<UserDetails>()
Error is:
Instance argument: cannot convert from 'System.Collections.Generic.IEnumerable<mynamespace.UserDetails>' to 'System.Collections.Generic.IEnumerable<System.Collections.IEnumerable>'
public class UserData
{
public int Index{ get; set; }
public string Name{ get; set; }
public string PersonalNo { get; set; }
public <List<UserDetails>> Details { get; set; }
}
public struct UserDetails
{
public int Age;
public string profession;
public string gender;
}
public IEnumerable<Userdata> GetUserData()
{
var context = new DatabaseEntities();
var Results =
from a in context.UserData
join b in context.UserDetails on a.Index equals b.Index into c
select new UserData{ Index = a.Index, Name = a.Name, PersonalNo = a.PersonalNo,
Details= c.ToList<UserDetails>() };
return Results;
}
Is there anybody who can help me out. In case more infos needed, please ask.
Thanks.
The following example is working. To get it working you can use object. See below the example:
public class UserData
{
public int Index{ get; set; }
public string Name{ get; set; }
public string PersonalNo { get; set; }
public List<object> Details { get; set; }
}
public class UserDetails
{
public int Age;
public string profession;
public string gender;
}
public IEnumerable<Userdata> GetUserData()
{
var context = new DatabaseEntities();
var Results =
from a in context.UserData
join b in context.UserDetails on a.Index equals b.Index into c
select new UserData{ Index = a.Index, Name = a.Name, PersonalNo = a.PersonalNo,
Details= c.ToList<object>() };
return Results;
}
The Type in the Watch windows is names as "object {MyTestApp.UserDetails}"
So basically it is working as I did it. But I need to cast it appropriate. Possibly now it is easier to help me.
Any suggestions?

Categories