How to join Enum with LINQ Query - c#

I have user table (Default ApplicationUser Table from IdentityUser by ASP.CORE)
and I have added additional field for RoleType. There is also an Enum I added to specify Role Definition.
public enum Roles
{
Administrator = 1,
Headquarters = 2,
Branch = 3,
Driver = 4,
Client = 5
}
Now I want to show all the users in a view as a table along with role description.
I am unable to make LINQ query with Enum & User table using LINQ join.

To get the list of Roles from the enum use:
var roles = Enum.GetValues(typeof(Roles)).Cast<Roles>()
.Select(r => new { Value = (int)r, Name = r.ToString() }).ToList();
you can then use this in your Linq query, for example:
var roles = Enum.GetValues(typeof(Roles)).Cast<Roles>()
.Select(r => new { Value = (int)r, Name = r.ToString() }).ToList();
var users = from u in ApplicationUser
join r in roles on u.Role equals r.Value
select new {Name = u.Name, RoleId = u.Role, RoleDescription = r.Name} ;
A simpler way without the Enum.GetValues is:
var users = from u in ApplicationUser
select new {Name = u.Name, RoleId = u.Role, RoleDescription = (Roles)r.Role.ToString()} ;

var xx = from u in _context.Users
.Select(x => new ApplicationUserList
{ Firstname = x.Firstname,
RoleType = ((Roles)x.RoleId).ToString()
});

// This join is performed in memory
var results =
from e in Enum.GetValues(typeof(Roles)).Cast<Roles>()
join r in ApplicationUser on e equals r.Roles into rs
from r in rs.DefaultIfEmpty()
select new { Roles = e, Count = r?.Count ?? 0};

If I understand your question, you should first convert enum to dictionary an Join between what you need, here is an example:
static void Main(string[] args)
{
ApplicationUser a = new ApplicationUser();
a.userName = "a";
a.role = 1;
ApplicationUser b = new ApplicationUser();
b.userName = "b";
b.role = 3;
List<ApplicationUser> alist=new List<ApplicationUser>();
alist.Add(a);
alist.Add(b);
Dictionary<int, string> DicRoles = new Dictionary<int, string>();
var vals = Enum.GetValues(typeof(Roles));
foreach (var val in vals)
{
DicRoles.Add((int)val, val.ToString());
}
var result = from t in alist
join x in DicRoles on t.role equals x.Key
select new {t.userName,x.Value };
}
public enum Roles:int
{
Administrator = 1,
Headquarters = 2,
Branch = 3,
Driver = 4,
Client = 5
}
}
public class ApplicationUser
{
public string userName { get; set; }
public int role { get; set; }
}

Related

Sorting multiple LINQ results of different models as one list in MVC

I am trying to sort a list of users that are either students, colleagues or guests and sort them in my view based on their names.
Here is the code:
public ActionResult Index()
{
var db = new PraktikumDataContext();
var model = new List<AdminUserListItem>();
var studs = (from stud in db.Students select new AdminUserListItem() {Name = stud.FH_Angehörige.Name, LastLogin = stud.FH_Angehörige.FE_Nutzer.Letzter_Login, Rolle = "Student"}).OrderBy(stud => stud.Name);
model.AddRange(studs);
var mits = (from mit in db.Mitarbeiters select new AdminUserListItem() {Name = mit.FH_Angehörige.Name, LastLogin = mit.FH_Angehörige.FE_Nutzer.Letzter_Login, Rolle = "Mitarbeiter"}).OrderBy(stud => stud.Name);
model.AddRange(mits);
var gasts = (from gast in db.Gasts select new AdminUserListItem() {Name = gast.Name, LastLogin = gast.FE_Nutzer.Letzter_Login, Rolle = "Gast"}).OrderBy(stud => stud.Name);
model.AddRange(gasts);
model = model.OrderByDescending()
return View(model);
}
What I've already done with OrderBy sorts each model in it's own scope, however since I have 3 models, I am a little bit confused now how to somehow make them to be seen as one list and then sort them and show them in my website.
Consider using a LINQ union that makes a single call to the server:
public ActionResult Index()
{
var db = new PraktikumDataContext();
var model =
(from stud in db.Students
select new AdminUserListItem()
{
Name = stud.FH_Angehörige.Name,
LastLogin = stud.FH_Angehörige.FE_Nutzer.Letzter_Login,
Rolle = "Student"}
).Union(
from mit in db.Mitarbeiters
select new AdminUserListItem()
{
Name = mit.FH_Angehörige.Name,
LastLogin = mit.FH_Angehörige.FE_Nutzer.Letzter_Login,
Rolle = "Mitarbeiter"}
).Union(
from gast in db.Gasts
select new AdminUserListItem()
{
Name = gast.FH_Angehörige.Name,
LastLogin = gast.FE_Nutzer.Letzter_Login,
Rolle = "Gast"}
)
.OrderByDescending(a => a.Name)
.ToList();
return View(model);
}

Select properties for particular entities LINQ

You can probably see the result I want to get. It's easy using loop, but I can't understand how to achieve such result using LINQ extension methods
I have two contexts that target one DB. ApplicationUser is authentication class, and profileDTO profile info that I get from same DB.
ProfileDTO properties: string Id, string FirstName, string LastName
Both tables share same ID but are not connected neither through navigation properties nor any references in the DB.
IEnumerable<ViewModels.User.IndexViewModel> model;
IEnumerable<Models.ApplicationUser> users;
var profilesDtos = _profileService.GetAll();
using (var context = new Models.ApplicationDbContext())
{
users = context.Users.ToList();
}
model = users.Select(user =>
new ViewModels.User.IndexViewModel
{
Id = user.Id,
Email = user.Email,
PhoneNumber = user.PhoneNumber,
LockedOutTill = user.LockoutEndDateUtc ?? default(DateTime),
Roles = UserManager.GetRoles(user.Id)
});
foreach (var user in model)
{
var userProfile = profilesDtos.FirstOrDefault(o => o.Id == user.Id);
if (userProfile != null)
{
user.FirstName = userProfile.FirstName;
user.LastName = userProfile.LastName;
}
};
I want to get all users but with Names set only in those who have profiles.
You can use left join in Linq, like below -
IEnumerable<ViewModels.User.IndexViewModel> model;
IEnumerable<Models.ApplicationUser> users;
var profilesDtos = _profileService.GetAll();
using (var context = new Models.ApplicationDbContext())
{
users = context.Users.ToList();
}
model = (from u in users
join p in profilesDtos on u.Id equals p.Id into tempTbl
from up in tempTbl.DefaultIfEmpty()
select new ViewModels.User.IndexViewModel
{
Id = u.Id,
Email = u.Email,
PhoneNumber = u.PhoneNumber,
LockedOutTill = u.LockoutEndDateUtc ?? default(DateTime),
Roles = UserManager.GetRoles(u.Id),
FirstName = up!= null? up.FirstName : string.Empty;
LastName = up!= null? up.LastName : string.Empty;
}).ToList();
First of all I would suggest to update your context to setup such property. If you can't do this use JOIN:
var result =
from user in context.Users
join profile in userProfiles on user.ID equals profile.ID
select new ViewModels.User.IndexViewModel {
Id = user.Id,
FirstName = profile.FirstName,
...
}
As a solution, you can just join them.
MSDN
Plus DefaultIfEmpty statement.

Concatenate Int to String in LINQ Query

I have the following LINQ query.
var providers = from c in Repository.Query<Company>()
where !c.IsDeleted
select new { c.Description, Id = "C" + c.Id };
I'm trying to concatenate the ID to "C". So, for example, if c.Id is 35 then the result should be "C35".
This obviously doesn't work because you can't add an integer (c.Id) to a string. I could easily resolve this in C# using string.Format() or converting the type. But how can I do this in LINQ?
Try using SqlFunctions.StringConvert Method:
var xd = (from c in Repository.Query<Company>()
where !c.IsDeleted
select new { c.Description, Id = "C" + SqlFunctions.StringConvert((double)c.Id).Trim()});
When you need functionality of .NET only in preparing the result (as opposed to, say, filtering, which should be done on RDBMS side to avoid bringing too much data in memory) the common trick is to complete the conversion in memory using the AsEnumerable method:
var providers = Repository.Query<Company>()
.Where(c => !c.IsDeleted)
.Select(c => new { c.Description, c.Id }) // <<== Prepare raw data
.AsEnumerable() // <<== From this point it's LINQ to Object
.Select(c => new { c.Description, Id = "C"+c.Id }); // <<== Construct end result
The code that you have written will work fine. Here is a mock up of the same code and it outputs the Id's
class Company
{
public string Description { get; set; }
public int Id { get; set; }
public bool IsDeleted { get; set; }
}
static void Main()
{
//setup
var list = new List<Company>();
list.Add(new Company
{
Description = "Test",
Id = 35,
IsDeleted = false
});
list.Add(new Company
{
Description = "Test",
Id = 52,
IsDeleted = false
});
list.Add(new Company
{
Description = "Test",
Id = 75,
IsDeleted = true
});
/* code you are looking for */
var providers = from c in list
where !c.IsDeleted
select new { c.Description, Id = "C" + c.Id };
foreach (var provider in providers)
{
Console.WriteLine(provider.Id);
}
Console.ReadKey();
}
What about string format
var providers = from c in Repository.Query<Company>()
where !c.IsDeleted
select new { c.Description, Id = "C" + c.Id.ToString() };

C# linq group by on different keys in the same entity

I've been toying with this for a while and just can't get it. I'm new to Linq, C# and these Lambda things.
What I want to do is group entities according to two properties on each entity. It's a Message entity:
Message
{
int UserId; //The user generating the message
int UserIdTo; //The receiver of the message
|...| // Other stuff
}
So, I want it so that these UserId=5, UserIdTo=6 and UserId=6, UserIdTo=5 would be in the same group.
Here's my start:
var groupList = (from m in db.Messages
where m.UserId == userId || m.UserIdTo == userId
join u in db.Users on m.UserId equals u.UserId
join w in db.Users on m.UserIdTo equals w.UserId
orderby m.MessageTimestamp descending
select new DomMessage
{
MessageId = m.MessageId,
MessageContent = m.MessageContent,
MessageTimestamp = m.MessageTimestamp,
UserId = m.UserId,
UserIdTo = m.UserIdTo,
ScreenName = u.ScreenName,
ScreenName2 = w.ScreenName
}).GroupBy(m=>m.UserId == userId)
.ToList();
This does the first bit of grouping by UserId, but I'm stuck on trying to extend this so that where any UserId value in the resulting group equals the UserIdTo somewhere else add that to this group?
EDIT: I need the result to go to a List because there is other stuff I need to do with it...
Thanks!
Try this:
var payload = new[]
{
new{ To = 1, From = 2, Message = "msj1" },
new{ To = 1, From = 2, Message = "msj2" },
new{ To = 2, From = 1, Message = "msj3" },
new{ To = 4, From = 1, Message = "msj4" },
new{ To = 1, From = 3, Message = "msj5" }
};
var groupped = payload.Select(x => new { Key = Math.Min(x.To, x.From) + "_" + Math.Max(x.To, x.From), Envelope = x }).GroupBy(y => y.Key).ToList();
foreach (var item in groupped)
{
Console.WriteLine(String.Format(#"Group: {0}, messages:", item.Key));
foreach (var element in item)
{
Console.WriteLine(String.Format(#"From: {0} To: {1} Message: {2}", element.Envelope.From, element.Envelope.To, element.Envelope.Message));
}
}
Try the following GroupBy expression:
.GroupBy(m => Math.Min(m.UserId, m.UserIdTo) + ',' + Math.Max(m.UserId, m.UserIdTo))
I think this is the easiest way:
var groupList = from a in (
from m in db.Messages
where m.UserId == userId || m.UserIdTo == userId
join u in db.Users on m.UserId equals u.UserId
join w in db.Users on m.UserIdTo equals w.UserId
select new
{
MessageId = m.MessageId,
MessageContent = m.MessageContent,
MessageTimestamp = m.MessageTimestamp,
UserId = m.UserId,
UserIdTo = m.UserIdTo,
ScreenName = u.ScreenName,
ScreenName2 = w.ScreenName
})
group a by new {
UserId = a.UserId,
UserIdTo = a.UserIdTo
} into grp
orderby grp.Max(a => a.MessageTimestamp) descending
select new
{
UserId = grp.Key.UserId
UserIdTo = grp.Key.UserIdTo,
MessageId = grp.Max(a => a.MessageId),
MessageContent = grp.Max(a => a.MessageContent),
MessageTimestamp = grp.Max(a => a.MessageTimestamp),
ScreenName = grp.Max(a => a.ScreenName),
ScreenName2 = grp.Max(a => a.ScreenName2)
}
You have to tell it what to do with the fields you are not grouping by. In this case I got the MAX value for each.

Hierarchical layering in Linq

If I have a set of employee data similar to:
var users = new[]
{
new {SupervisorId = "CEO", UserId = "CEO", UserName = "Joe"},
new {SupervisorId = "CEO", UserId = "CIO", UserName = "Mary"},
new {SupervisorId = "CIO", UserId = "XDIR", UserName = "Ed"},
new {SupervisorId = "CIO", UserId = "YDIR", UserName = "Lisa"},
new {SupervisorId = "XDIR", UserId = "AMNGR", UserName = "Steve"},
new {SupervisorId = "AMNGR", UserId = "ASUP", UserName = "Lesley"}
};
Would it be possible to use Linq to add hierarchical layers, in the sense that:
CEO = 1 (top)
CIO = 2 (2nd level)
XDIR and YDIR = 3 (3rd level)
AMNGR = 4 (etc)
ASUP = 5 (etc)
I've been able to group the employees according to SupervisorId, but not sure how to make the "level" happen.
var userGroups = from user in users
group user by user.SupervisorId into userGroup
select new
{
SupervisorId = userGroup.Key,
Level = ??????
Users = userGroup.ToList()
};
foreach (var group in userGroups)
{
Console.WriteLine("{0} - {1} - {2}", group.SupervisorId, group.Level, group.Users.Count);
}
Many thanks.
I would add a ranking to your linq "user object"
public class User{
public string SupervisorId {get;set;}
public string UserId {get;set;}
public string UserName {get;set;}
public int Level {get { return GetRank(SupervisorId ) ; } }
private int GetRank(string userId){
if(string.IsNullOrEmpty(userId)){
//Bad case, probably want to use a very large number
return -1;
}
int level = 0;
switch(userId){
case "CEO":
level = 0;
break;
//insert others here
}
}
}
Then your Linq you would add a join.
var userGroups = from user in users
join super in users on user.SupervisorId equals super.UserId
group user by user.SupervisorId into userGroup
select new
{
SupervisorId = userGroup.Key,
Level = super.Level,
Users = userGroup.ToList()
};
ILookup<string, User> subordLookup = users
.ToLookup(u => u.SupervisorId);
foreach(User user in users)
{
user.Subordinates = subordLookup[user.UserId].ToList();
}
User userHierarchy = user.Single(u => u.UserId == "CEO");
Disclaimers:
Does not handle multiple CEOs.
Preserves circular relationships.
Leaves orphans behind.
Is this what you are looking for?
var levels = new[]
{
new { Level = 1, LevelName = "CEO" },
new { Level = 2, LevelName = "CIO" },
new { Level = 3, LevelName = "XDIR" },
new { Level = 3, LevelName = "YDIR" },
new { Level = 4, LevelName = "AMNGR" },
new { Level = 5, LevelName = "ASUP" }
};
var userGroups = from user in users
join level in levels on
user.UserId equals level.LevelName
group new{ User = user, Level = level.Level } by new { SuperId = user.SupervisorId, Level = level.Level } into userGroup
select new
{
SupervisorId = userGroup.Key.SuperId,
Level = userGroup.Key.Level,
Users = userGroup.ToList()
};
Update
Heres one way to create a lookup table for each level. Its fairly and I dont know how it will scale. Obviously, you'll need to adapt it to pull the rows from your database.
Define a class to hold our lookup table
public class user{
public string SupervisorId;
public string UserId;
public int Level;
}
Then we get a unique list of UserId/SupervisorId combinations and loop through the list calculating the level for each combination by 'walking' up the tree.
var uniqueusers = (new user[]
{
new user {SupervisorId = "CEO", UserId = "CEO"},
new user {SupervisorId = "CEO", UserId = "CIO"},
new user {SupervisorId = "CIO", UserId = "XDIR"},
new user {SupervisorId = "CIO", UserId = "YDIR"},
new user {SupervisorId = "XDIR", UserId = "AMNGR"},
new user {SupervisorId = "AMNGR", UserId = "ASUP"}
}).Distinct();
foreach (var item in uniqueusers)
{
int level = 0;
user CurrentUser = item;
while (CurrentUser.UserId != CurrentUser.SupervisorId){
CurrentUser = uniqueusers.Where(c => c.UserId == CurrentUser.SupervisorId).FirstOrDefault();
level++;
}
item.Level = level;
}
Now you can use the uniqueusers as a lookup table to determine the level for your query. eg
private int GetLevel(string userId){
return uniqueusers.Where(c => c.UserId == userId).FirstOrDefault().Level;
}
You could probably even combine this into a single step with a little effort.

Categories

Resources