I wrote the following query to rotate data by converting rows to columns using LINQ:
var query = from p in context.PrivilegesTable
group p by p.Type into g
select new Privileges
{
Type = g.Key,
AllowRead = g.Any(p => p.Seq == 1),
AllowAdd = g.Any(p => p.Seq == 2)
AllowEdit = g.Any(p => p.Seq == 3)
AllowDelete = g.Any(p => p.Seq == 4)
};
I think there's a better way to implement it. for more details, read the following:
I have the following Privileges table:
Type Seq
1 1
2 1
2 2
3 1
3 2
3 3
And the following Privileges class:
class Privileges
{
public int ID { get; set; }
public Type Type { get; set;}
public int AllowRead { get; set; } // when sequence = 1
public int AllowAdd { get; set; } // when sequence = 2
public int AllowEdit { get; set; } // when sequence = 3
public int AllowDelete { get; set; } // when sequence = 4
}
Now, I want to create a List<Prvileges> using LINQ to have the following result:
[Type, AllowRead, AllowAdd, AllowEdit, AllowDelete]
[1, True, False, False, False]
[2, True, True, False, False]
[3, True, True, True, False]
Is my query bad for performance ?
Is there a better one ?
I'd suggest using a enum to represent the privileges you can have.
[Flags]
enum Privilege
{
None = 0,
Read = 1,
Add = 2,
Edit = 4,
Delete = 8,
}
In that way you can store a single value to represent all your privileges. Then your Privileges class would be like this:
class Privileges {
public int ID { get; set; }
public Type Type { get; set;}
private Privilege m_Privileges;
public bool AllowRead {
get { return HasPrivilege(Privilege.Read); }
set { CheckPrivilege(Privilege.Read, value); }
}
public bool AllowAdd {
get { return HasPrivilege(Privilege.Add); }
set { CheckPrivilege(Privilege.Add, value); }
}
public bool AllowEdit {
get { return HasPrivilege(Privilege.Edit); }
set { CheckPrivilege(Privilege.Edit, value); }
}
public bool AllowDelete {
get { return HasPrivilege(Privilege.Delete); }
set { CheckPrivilege(Privilege.Delete, value); }
}
private bool HasPrivilege(Privilege p) {
return (m_Privileges & p) == p;
}
private void CheckPrivilege(Privilege p, bool owns) {
if (owns)
m_Privileges = m_Privileges | p;
else
m_Privileges = m_Privileges & (~p);
}
}
Of course this approach is only valid if you have a limited and known number of privileges.
Hope that helps
Your query performance will be abysmal because every Any does a join (L2S implementation problem). In SQL you would do a pivot clause. It can be simulated in L2S but the join problem remains (I reported this bug today on connect). There is no workaround to my knowledge, sorry. If perf is a problem, you must cache or use raw sql.
Edit: I think I have found a special case for you problem:
var query = from p in context.PrivilegesTable
let shift = 1 << p.Seq
group shift by p.Type into g
select new
{
Type = g.Key,
AllowMask = g.Sum(),
};
How cool is that? ;-) Unfortunately it is terrible code and works only up to 64 bit flags.
Related
I've got a list of objects, let's call the objects People. Each person has a list of vacation days they've requested. I'm trying to create a Linq query to find out how many people have requested the same vacation day. I'm not having any luck. Any advice, or perhaps a shove in the right direction?
You could create classes like this:
public class Person
{
public List<int> Vacations { get; set; }
public string Name { get; set; }
}
public class Vacation
{
public List<string> People { get; set; }
public int Day { get; set; }
public int PeopleCount => People?.Count ?? 0;
}
and then get a list of people who have booked each day like this:
public void Test()
{
var people = new List<Person>()
{
new Person() { Name = "Person1", Vacations = new List<int>() { 1, 15, 200, 364 } },
new Person() { Name = "Person2", Vacations = new List<int>() { 1, 15, 110, 210 } },
new Person() { Name = "Person3", Vacations = new List<int>() { 1, 15, 200 , 210} }
};
var vacations =
Enumerable.Range(0, 365)
.Select(d => new Vacation()
{
Day = d,
People = people.Where(p
=> p.Vacations.Contains(d)).Select(p => p.Name).ToList()
})
.ToList();
}
This should help:
class Customer
{
List<Vacation> vacationDays {get; set;}
}
public class Vacation : IEquatable<Vacation>
{
public string Name { get; set; }
public int VacationId { get; set; }
public override string ToString()
{
return "ID: " + VacationId + " Name: " + Name;
}
public override bool Equals(object obj)
{
if (obj == null) return false;
Vacation objAsVacation = obj as Vacation;
if (objAsVacation == null) return false;
else return Equals(objAsVacation);
}
public override int GetHashCode()
{
return VacationId;
}
public bool Equals(Vacation other)
{
if (other == null) return false;
return (this.VacationId.Equals(other.VacationId));
}
// Should also override == and != operators.
}
Now, you can use SelectMany here:
https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.selectmany?view=netframework-4.7.2
More on Contains here:
https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.contains?view=netframework-4.7.2
Start by flattening the list. This code will create a single, non-nested list containing one row for each person/date:
var flatList = people.SelectMany
(
p => p.VacationDays.Select( d => new { Person = p, Date = d } )
);
Then you can filter any way you want quite trivially:
var lookFor = DateTime.Parse("1/1/2019");
var entriesForJan01 = flatList.Where( f => f.Date == lookFor );
Click this link for a working example on DotNetFiddle
This can be done in many ways using LINQ, but I would like to suggest a more efficient solution if you want to do the lookup for multiple dates. My solution uses a Dictionary<DateTime,int> which acts as a counter for the days we have encountered. Thanks to the fact that Dictionary lookup is in constant time complexity (O(1)), this solution will be very efficient when you need to check the number of occurrences for multiple dates or even for all dates.
var dateOccurrences = new Dictionary<DateTime, int>();
foreach (var vacationDate in people.SelectMany(p => p.Vacations))
{
//check if we already have this date in the dictionary
if (!dateOccurrences.TryGetValue(vacationDate.Date, out int previousOccurrences))
{
//never seen before
previousOccurrences = 0;
}
//add one occurrence
dateOccurrences[vacationDate] = previousOccurrences + 1;
}
Now for lookup just use TryGetValue again. Alternatively, you can foreach over all the dictionary entries:
foreach (var pair in dateOccurrences)
{
Console.WriteLine(pair.Key);
Console.WriteLine(pair.Value);
}
I have a DBSet which is db.Company and it contains 3 records like
id name is_active
1 company1 1
2 company2 1
3 company3 1
My Created class to transfer the records:
public class CompanyFinal
{
public int id { get; set; }
public string name { get; set; }
public string selected { get; set; }
}
My LINQ looks like this:
(from h in db.Company where h.is_active == 1 select new CompanyFinal { id = h.id, name = h.name, selected = "false" }).ToList();
No doubt this LINQ is working but i need to make the first record to be selected="true" which is the company 1 while doing it in LINQ.
How could i do that? Is it possible?
Thanks in advance
Since you're making a list, I think that computational the fasted method, which is also the best maintainable (understood by others) would be to just assign the first element:
(Using method syntax, showing an alternative select)
var resultingList = db.Company
.Where(company => company.is_active == 1)
.Select(company => new CompanyFinal()
{
id = company.id,
name = company.name,
selected = "false",
})
.ToList();
if (resultingList.Any())
resultingList[0].selected = true;
If you are not sure whether you want to convert it to a list, for instance because you want to concatenate other Linq functions after it, consider using this overload of Enumerable.Select
var result = db.Company
.Where(company => company.is_active == 1)
.Select( (company, index) => new CompanyFinal()
{
id = company.id,
name = company.name,
selected = (index == 0) ? "true" : "false",
});
By the way, consider changing your CompanyFinal class such that selected is a bool instead of a string, that would make your code less error prone. For instance, after construction Selected is neither "true" nor "false". Users are able to give it a value like "TRUE" or String.Empty
If your class has users that really need a string property selected instead of a Boolean property consider keeping a get property for it:
public class CompanyFinal
{
public int id { get; set; }
public string name { get; set; }
public bool Selected {get; set;}
// for old user compatability:
public string selected
{
get { return this.Selected ? "true" : "false"; }
}
}
The following will go wrong:
if (companyFinals[0].selected == "True") ...
versus:
if (companyFinals[0].Selected)
What if we define a local variable
var counter = 1;
(from h in db.Company where h.is_active == 1 select new CompanyFinal { id = h.id, name = h.name, selected = (counter++ == 1 ? "true" :"false") }).ToList();
From your result:
var companies = (from h in db.Company
where h.is_active == 1 select h).ToList();
var companyFinals = (from h in companies
select new CompanyFinal {
id = h.id,
name = h.name,
selected = "false"
}).ToList();
var firstCompany = companies.FirstOrDefault();
if (firstCompany != null) {
firstCompany.selected = true;
}
// save result back to database
There is no way to do it in 1 operation other than writing a stored procedure.
I have a class Guest
class Guest
{
bool f = true;
bool g = false;
bool s = false;
string name = "";
}
And a List where all the included -> var g = new List<Guest>();
It can be only one of the booleans true.
At the start come all the f guest then g guest in the middle and at last the s guest.
But all guest must be sorted alphabetic in f or g or in the a group.
Maybe so?
var query = (from Guest in GuestList
orderby Guest.f, Guest.g, Guest.s, Guest.name
select Guest);
I'm just not it.
Thanks and greetz, Malte
Sounds like pretty typical nested sorting. There is no need to group.
var result = source
.OrderBy(guest =>
guest.f ? 1 :
guest.g ? 2 :
guest.s ? 3 :
4)
.ThenBy(guest => guest.name);
For those who are unfamiliar with the syntax, allow me to read the code.
In the call to OrderBy, there is a lambda function which uses a chained ternary operator to generate a sort key for each row. OrderBy sorts by this sort key.
The result of OrderBy is an IOrderedEnumerable<Guest> and is passed to ThenBy.
ThenBy preserves the sorting of the prior ordering operations and works to break ties.
This should work. The Sort and OrderBy will use the CopmpareTo() method
public class Guest : IComparable<Guest>
{
public bool f { get; set; }
public bool g { get; set; }
public bool s { get; set; }
public string name { get; set; }
public int CompareTo(Guest other)
{
int results = 0;
if (this.f)
{
if (other.f)
{
results = this.name.CompareTo(other.name);
}
else
{
results = 1;
}
}
else
{
if (other.f)
{
results = -1;
}
else
{
if (this.g)
{
if (other.g)
{
results = this.name.CompareTo(other.name);
}
else
{
results = 1;
}
}
else
{
if (other.g)
{
results = -1;
}
else
{
if (this.s)
{
if (other.s)
{
results = this.name.CompareTo(other.name);
}
else
{
results = 1;
}
}
else
{
results = this.name.CompareTo(other.name);
}
}
}
}
}
return results;
}
Below is simpler method which will even work with when more than one property is true. Notice that I used 1,2,4 instead of 1,2,3 as in other solutions. 1,2,3 has issue that there is more than one way of getting 3 when multiple properties are true.
public class Guest : IComparable<Guest>
{
public bool f { get; set; }
public bool g { get; set; }
public bool s { get; set; }
public string name { get; set; }
public int CompareTo(Guest other)
{
int results = 0;
int thisRank = (this.f ? 1 : 0) + (this.g ? 2 : 0) + (this.s ? 4 : 0);
int otherRank = (other.f ? 1 : 0) + (other.g ? 2 : 0) + (other.s ? 4 : 0);
if (thisRank == otherRank)
{
results = this.name.CompareTo(other.name);
}
else
{
results = thisRank.CompareTo(otherRank);
}
return results;
}
}
here is David B's example with more common syntax for the if statements.
var result = source
.OrderBy(guest => { if (guest.f == true) return 1 else
if (guest.g == true) return 2 else
if (guest.s == true) return 3 else return 4;})
.ThenBy(guest => guest.name);
I'm trying to get an ID from a collection with a bool to tell me if that ID is associated with another value.
So, I have this:
public class RoleAssignment
{
[Key]
public int RoleAssignmentId { get; set; }
public Guid AssigneeId { get; set; }
public int RoleId { get; set; }
}
With this data:
var rAssnd = new List<RoleAssignment>{
{ 2, 0251F0D6-F2C9-E511-8C3C-00215E466552, 48 }
{ 3, 0251F0D6-F2C9-E511-8C3C-00215E466552, 49 }
{ 4, 0251F0D6-F2C9-E511-8C3C-00215E466552, 52 }
{ 5, F48459F5-469F-E511-8172-00215E466552, 44 }
}
So, if I am checking for RoleId 49, I would like to get a result set like this:
0251F0D6-F2C9-E511-8C3C-00215E466552, true
F48459F5-469F-E511-8172-00215E466552, false
Right now I'm trying this:
var results = selected.GroupBy (s => s.AssigneeId, s => s.RoleId == proposalRole);
But this gives me the Guid and a list.
Could I have the second value be something like Any(s.RoleId == proposalRole)?
First group, then use any to check each group for your rule.
var results = rAssnd
.GroupBy(s => s.AssigneeId)
.Select(g => new
{
g.Key,
hasRule = g.Any(s => s.RoleId == proposalRole)
});
Trying to use Distinct() using a custom comparer and it gives me the error:
cannot be inferred from the usage. Try specifying the type arguments explicitly
The Default comparer works fine but doesn't give the results I expect of course. How can I fix this?
public class TimeEntryValidation
{
public string EmployeeID { get; set; }
public string EmployeeLocation { get; set; }
public string EmployeeDepartment { get; set; }
public int RowIndex { get; set; }
}
public class MyRowComparer : IEqualityComparer<TimeEntryValidation>
{
public bool Equals(TimeEntryValidation x, TimeEntryValidation y)
{
return (x.EmployeeDepartment == y.EmployeeDepartment && x.EmployeeLocation == y.EmployeeLocation);
}
public int GetHashCode(TimeEntryValidation obj)
{
return obj.EmployeeID.GetHashCode();
}
}
void Query(List<TimeEntryValidation> listToQuery)
{
var groupedData =
from oneValid in listToQuery
group oneValid by oneValid.EmployeeID
into g
where g.Count() > 1
select new {DoubleItems = g};
var listItems = groupedData.Distinct(new MyRowComparer());
}
The type of groupedData is some IEnumerable<{an anonymous type}> whereas MyRowComparer is IEqualityComparer<TimeEntryValidation>
It's unclear whether you intended listItems to be a list of groups, or whether you wanted the actual items themselves.
If it's the latter, you probably want something like this:
void Query(List<TimeEntryValidation> listToQuery)
{
var groupedData = from oneValid in listToQuery
group oneValid by oneValid.EmployeeID
into g
where g.Count() > 1
select g ;
var listItems = groupedData.SelectMany(group => group).Distinct(new MyRowComparer());
//listItems is now an IEnumerable<TimeEntryValidation>
}