Given we cannot use a function in this scenario, what could be good way of incorporation GetPreferredName function within Linq query?
List<Employee> Employees = (from d in context.Employees
join a in context.Address on d.ID equals a.EmployeeID
select new Employee
{
Id = d.Id,
PreferredName = GetPreferredName(d.FirstName, d.MiddleName, d.LastName, d.Alias),
StreetAddress = a.StreetAddress
}).ToList();
private string GetPreferredName(string firstName, string middleName, string lastName, string dnsName)
{
if (!string.IsNullOrEmpty(firstName))
return firstName;
else if (!string.IsNullOrEmpty(middleName))
return middleName;
else if (!string.IsNullOrEmpty(lastName))
return lastName;
else if (!string.IsNullOrEmpty(dnsName))
return dnsName;
return "";
}
What you can do is enumerate from the database first, then do a new select to run your extra code:
var Employees =
(from d in context.Employees
join a in context.Address on d.ID equals a.EmployeeID
select new //select the important bits we'll need in memory
{
Employee = d,
Address = a,
})
.AsEnumerable() //AsEnumerable() it to make it enumerate from the database, now everything you need is in memory
.Select(x => new Employee
{
Id = x.Employee.Id,
PreferredName = GetPreferredName(x.Employee.FirstName, x.Employee.MiddleName, x.Employee.LastName, x.Employee.Alias),
StreetAddress = x.Address.StreetAddress
})
.ToList();
Also, with linq, you don't need to do these joins manually, you could change it to this:
var Employees = context.Employees.Select(e =>
select new //select the important bits we'll need in memory
{
Employee = e,
Address = e.Address, //join is done for you!
})
.AsEnumerable() //AsEnumerable() it to make it enumerate from the database, now everything you need is in memory
.Select(x => new Employee
{
Id = x.Employee.Id,
PreferredName = GetPreferredName(x.Employee.FirstName, x.Employee.MiddleName, x.Employee.LastName, x.Employee.Alias),
StreetAddress = x.Address.StreetAddress
})
.ToList();
You can use ?? null-coalescing operator.
PreferredName = d.FirstName ?? d.MiddleName ?? d.LastName ?? d.Alias
Or ?: conditional operator
PreferredName = (d.FirstName != null && d.FirstName != "") ? d.FirstName :
((d.MiddleName != null && d.MiddleName != "") ? d.MiddleName :
((d.LastName != null && d.LastName != "") ? d.LastName : d.Alias))
Related
I have a Linq Query that is working as intended, but I need to add the code so that it will show me ONLY the people with the less cases assigned for an app that is been used to treat customers inquiries. The idea behind the query is so that it will let me automatically assign inquiries randomly between those agents which have less assigned issues to cover.
As a simple example, lets say I have 5 agents with just 1 case each, I need to randomly assign one of them to an Inquire which has currently no agent assigned. So all I'm looking for is a way to actually get all the agents with the smallest number of cases assigned.
So far this is the full proof of concept code:
var inquires = new List<Inquire>();
var agents = new List<Agent>();
LoadData();
var assignationsPerAgent = (from agent in agents
join inq in inquires on agent equals inq.AssignedAgent into agentsInInquires
select new {
o_agent = agent,
casesAssignedTo = agentsInInquires.Count()
}).ToList();
//This works but is NOT the kind of solution I'm looking for
var min = assignationsPerAgent.Min(c => c.casesAsignedTo);
var agentWithMin = assignationsPerAgent.Where(a => a.casesAsignedTo == min);
Console.WriteLine();
void LoadData()
{
agents = new(){
new Agent{ Id = Guid.Parse("317d3d26-25c2-49da-aa4b-b7e49a1b9015"), Name = "Robert" },
new Agent{ Id = Guid.Parse("84188e21-8147-498f-bc2a-59874dc4a24a"), Name = "Corina" },
new Agent{ Id = Guid.Parse("90ca6658-95d4-4df4-a072-159087feddc0"), Name = "John" },
new Agent{ Id = Guid.Parse("34e091e4-cc7a-4222-9885-5de5bb5a0291"), Name = "Jack"},
new Agent{ Id = Guid.Parse("f22dcb4e-e927-4ddf-ae66-f37c0de6753d"), Name = "Samuel"}
};
inquires = new(){
new Inquire{ CustomerName = "Paula", AsignedAgent = agents.Single(a => a.Id == Guid.Parse("317d3d26-25c2-49da-aa4b-b7e49a1b9015"))},
new Inquire{ CustomerName = "Barry", AsignedAgent = agents.Single(a => a.Id == Guid.Parse("84188e21-8147-498f-bc2a-59874dc4a24a"))},
new Inquire{ CustomerName = "Bertie", AsignedAgent = agents.Single(a => a.Id == Guid.Parse("84188e21-8147-498f-bc2a-59874dc4a24a"))},
new Inquire{ CustomerName = "Herman", AsignedAgent = agents.Single(a => a.Id == Guid.Parse("90ca6658-95d4-4df4-a072-159087feddc0"))},
new Inquire{ CustomerName = "Ashley", AsignedAgent = agents.Single(a => a.Id == Guid.Parse("317d3d26-25c2-49da-aa4b-b7e49a1b9015"))},
new Inquire{ CustomerName = "Tate", AsignedAgent = agents.Single(a => a.Id == Guid.Parse("90ca6658-95d4-4df4-a072-159087feddc0"))},
new Inquire{ CustomerName = "Bonnie", AsignedAgent = agents.Single(a => a.Id == Guid.Parse("90ca6658-95d4-4df4-a072-159087feddc0"))},
new Inquire{ CustomerName = "Tabitha", AsignedAgent = agents.Single(a => a.Id == Guid.Parse("90ca6658-95d4-4df4-a072-159087feddc0"))},
new Inquire{ CustomerName = "Ashley", AsignedAgent = agents.Single(a => a.Id == Guid.Parse("34e091e4-cc7a-4222-9885-5de5bb5a0291"))},
new Inquire{ CustomerName = "Josie", AsignedAgent = agents.Single(a => a.Id == Guid.Parse("84188e21-8147-498f-bc2a-59874dc4a24a"))},
new Inquire{ CustomerName = "Kelly", AsignedAgent = agents.Single(a => a.Id == Guid.Parse("84188e21-8147-498f-bc2a-59874dc4a24a"))},
new Inquire{ CustomerName = "Kelly", AsignedAgent = agents.Single(a => a.Id == Guid.Parse("f22dcb4e-e927-4ddf-ae66-f37c0de6753d"))},
};
}
#nullable disable
class Inquire
{
private Guid _id;
public Guid Id
{
get
{
return _id;
}
private set
{
_id = Guid.NewGuid();
}
}
public string CustomerName { get; set; }
public Agent AsignedAgent { get; set; }
}
#nullable disable
class Agent
{
public Guid Id {get; set;}
public string Name { get; set; }
}
Please take in consideration that the assignationsPerAgent variable is simulating data coming from a query in the database (The actual query is below). If the "easy" way to do this comes from the SQL that it's also an acceptable solution.
SELECT u.Id, COUNT(g.Id) as QtyAsig
FROM Users as u
LEFT JOIN GeneralInquires AS g ON u.Id = g.UserId
GROUP BY u.Id
ORDER BY COUNT(g.Id)
What I'm getting from this Query is:
Id QtyAsig
8A21A6D2-0CEC-4F5C-2A6B-08DA60967E94 1
323C8D1A-2FAE-4ECC-D7A2-08DA6098F19A 1
BA485F3C-C44A-4FE5-9BFA-08DA64EF283A 1
8F0E856E-FA0B-4167-BBEF-08DA6451FA81 2
40952727-5C76-4902-9C4F-08DA638B3068 3
DD51085A-5BE3-4872-F4B5-08DA6E7828AA 4
What I need is:
Id QtyAsig
8A21A6D2-0CEC-4F5C-2A6B-08DA60967E94 1
323C8D1A-2FAE-4ECC-D7A2-08DA6098F19A 1
BA485F3C-C44A-4FE5-9BFA-08DA64EF283A 1
Thank you in advance!
check this
var assignationsPerAgent = (from agent in agents
join inq in inquires on agent equals inq.AsignedAgent into agentsInInquires
from inq in agentsInInquires.DefaultIfEmpty()
select new
{
o_agent = agent,
casesAssignedTo = agentsInInquires.Count()
}).GroupBy(x=>x.casesAssignedTo).OrderBy(x=>x.Key).FirstOrDefault();
Linq doesn't require one statement and splitting results has no impact on performance. Try following :
List<Inquire> sortedInquires = inquires.OrderBy(x => x.AsignedAgent.Id).ToList();
var results = (from a in agents
//join i in sortedInquires on a.Id equals i.Id
join i in sortedInquires on a.Id equals i.AssignedAgent
select new { agent = a, inquires = i}
).GroupBy(x => x.agent.Id)
.Select(x => x.First())
.ToList();
I have search method with gets as parameter some DTO and make where filtering dynamically by DTO properties. The problem I get when using reflection is that I should explicitly cast to a property type that I am using. Is there a way to make it dynamic too so that the method that that make filtering can work without knowing type of a property. I am using Dynamic Ling Library.
CODE:
public async Task<ServiceResponceWithData<List<SearchResponseDTO>>> SearchAsync(SearchDTO model)
{
var users = from user in _userManeger.Users
join ur in _context.UserRoles
on user.Id equals ur.UserId
select new SearchResponseDTO
{
UserName = user.UserName,
Email = user.Email,
PhoneNumber = user.PhoneNumber,
FirstName = user.FirstName,
LastName = user.LastName,
Roles = _roleManager.Roles
.Where(c => c.Id == ur.RoleId)
.Select(c => c.Name)
.ToList()
};
var startResult = users;
var props = typeof(SearchDTO).GetProperties();
foreach (var prop in props)
{
if(prop.Name != "Role")
{
users = FilterWithWhereByStringTypeProperty(users, prop, model);
}
else
{
if (model.Role != null)
{
var results = users.Where(c => c.Roles.Any(r => r == model.Role));
users = results.Any() ? results : users;
}
}
}
var endResultList = startResult == users ? new List<SearchResponseDTO>() :await users.ToListAsync();
return new ServiceResponceWithData<List<SearchResponseDTO>>() { Data = endResultList, Success = true };
}
private IQueryable<SearchResponseDTO> FilterWithWhereByStringTypeProperty(IQueryable<SearchResponseDTO> collection,
PropertyInfo property,SearchDTO model )
{
var propertyName = property.Name;
var modelPropVal = property.GetValue(model);
if (modelPropVal == null) return collection;
string val = (string)modelPropVal;
string condition = String.Format("{0} == \"{1}\"", propertyName, val);
var fillteredColl = collection.Where(condition);
return fillteredColl.Any() ? fillteredColl : collection;
}
}
You could use System.Linq.Dynamic.Core.
In that case your code could be like (not tested):
var users = from user in _userManeger.Users
join ur in _context.UserRoles on user.Id equals ur.UserId
select new SearchResponseDTO
{
UserName = user.UserName,
Email = user.Email,
PhoneNumber = user.PhoneNumber,
FirstName = user.FirstName,
LastName = user.LastName,
Roles = _roleManager.Roles
.Where(c => c.Id == ur.RoleId)
.Select(c => c.Name)
.ToList()
};
IQueryable query = users;
var props = typeof(SearchDTO).GetProperties();
foreach (var prop in props)
{
if (prop.Name != "Role")
{
query = query.Where($"{prop.Name} == #0", prop.GetValue(model, null));
}
else
{
if (model.Role != null)
{
query = users.Where(c => c.Roles.Any(r => r == model.Role));
}
}
}
return query.ToList();
I am trying to do a LINQ query on several Mongo collections. All the collections have to be joined based on ApplicationId and an outer Join has to be done - so that persons that have no statuses are returned as well.
The JOIN part and everything around it works as expected. The problem is that when I add a filter to one of the collections, the whole thing breaks
An exception of type 'System.ArgumentException' occurred in System.Linq.Expressions.dll but was not handled in user code: 'Expression of type 'System.Collections.Generic.IEnumerable`1[CDM.Person]' cannot be used for parameter of type 'System.Linq.IQueryable`1[CDM.Person]' of method 'System.Linq.IQueryable`1[CDM.Person] Where[Person](System.Linq.IQueryable`1[CDM.Person], System.Linq.Expressions.Expression`1[System.Func`2[CDM.Person,System.Boolean]])''
Here is my query
var applications = _dbContext.GetCollection<Application>(typeof(Application).Name).AsQueryable().Where(
x => x.OrganizationID == TokenContext.OrganizationID);
var persons = _dbContext.GetCollection<Person>(typeof(Person).Name).AsQueryable().Where(p =>p.FirstName == "j");
var statuses = _dbContext.GetCollection<ApplicationStatus>(typeof(ApplicationStatus).Name).AsQueryable();
var mortgages = _dbContext.GetCollection<Mortgage>(typeof(Mortgage).Name).AsQueryable();
var statusQuery = from a in applications
join p in persons on a.ApplicationID equals p.ApplicationID
join s in statuses on a.ApplicationID equals s.ApplicationID into pas
join m in mortgages on a.ApplicationID equals m.ApplicationID into morgs
from subWHatever in pas.DefaultIfEmpty()
select new ApplicationStatusProjection
{
ApplicationId = a.ApplicationID,
FirstName = p.FirstName,
LastName = p.Surname,
Prefix = p.Prefix,
DateOfBirth = p.DateOfBirth,
Initials = p.Initials,
PostalCode = p.Addresses.First().PostalCode,
MortgageAmount = morgs.Sum(i => i.MortgageTotal) ?? 0,
StatusExpireAt = subWHatever.ExpireAt ?? DateTime.MinValue,
StatusMessageText = subWHatever.MessageText ?? "",
StatusMessage = subWHatever.MessageStatus ?? ""
};
if (!String.IsNullOrEmpty(orderBy))
{
statusQuery = statusQuery?.OrderBy(orderBy);
}
if (nrOfRecords != null)
{
statusQuery = statusQuery?.Take(nrOfRecords.Value);
}
// Execute the query
var result = statusQuery?.ToList();
return result;
I followed the guidelines here https://mongodb.github.io/mongo-csharp-driver/2.6/reference/driver/crud/linq/ and I also tried this
var statusQuery =
from a in applications
join p in persons on a.ApplicationID equals p.ApplicationID into pa
from paObject in pa.DefaultIfEmpty()
join s in statuses on paObject.ApplicationID equals s.ApplicationID into pas
But I got the same error as before.
Thank you in advance.
So after may tryouts I have discovered that you cannot filter before the join because of the way the LINQ query is translated to Mongo query.
The fix for this is to have the filtering afterwards, on the statusQuery object. In that case, the filtering has to happen on the projected object (so a new filter is needed).
See below how I solved it:
//get collections
var applications = _dbContext.GetCollection<Application>(typeof(Application).Name).AsQueryable().Where(
x => x.OrganizationID == TokenContext.OrganizationID);
var persons = _dbContext.GetCollection<Person>(typeof(Person).Name).AsQueryable();
var statuses = _dbContext.GetCollection<ApplicationStatus>(typeof(ApplicationStatus).Name).AsQueryable();
var mortgages = _dbContext.GetCollection<Mortgage>(typeof(Mortgage).Name).AsQueryable();
//query
var query = from a in applications
join p in persons on a.ApplicationID equals p.ApplicationID
join s in statuses on a.ApplicationID equals s.ApplicationID into applicationStatusView
join m in mortgages on a.ApplicationID equals m.ApplicationID into morgs
from subStatus in applicationStatusView.DefaultIfEmpty()
select new ApplicationStatusProjection
{
ApplicationId = a.ApplicationID,
FirstName = p.FirstName,
LastName = p.Surname,
Prefix = p.Prefix,
DateOfBirth = p.DateOfBirth,
Initials = p.Initials,
Addresses = p.Addresses ?? new List<Address>(),
PostalCode = p.Addresses.First().PostalCode,
MortgageAmount = morgs.Sum(i => i.MortgageTotal) ?? 0,
StatusExpireAt = subStatus.ExpireAt ?? DateTime.MinValue,
StatusMessageText = subStatus.MessageText ?? "",
StatusMessage = subStatus.MessageStatus ?? "",
StatusDate = subStatus.StatusDate
};
//filter & order
var filteredResult = ApplyFilters(query, searchCriteria);
if (!String.IsNullOrEmpty(orderBy))
{
filteredResult = filteredResult?.OrderBy(orderBy);
}
if (nrOfRecords != null)
{
filteredResult = filteredResult?.Take(nrOfRecords.Value);
}
// Execute the query
var result = filteredResult?.ToList();
return result;
And applying the filters (there is probably a smarter way to do this):
private IQueryable<ApplicationStatusProjection> ApplyFilters(IQueryable<ApplicationStatusProjection> query, ApplicationStatusProjectionSearch searchCriteria)
{
if (!string.IsNullOrEmpty(searchCriteria.FirstName))
{
query = query.Where(x => x.FirstName.ToLower().StartsWith(searchCriteria.FirstName));
}
if (!string.IsNullOrEmpty(searchCriteria.LastName))
{
query = query.Where(x => x.LastName.ToLower().StartsWith(searchCriteria.LastName));
}
if (!string.IsNullOrEmpty(searchCriteria.PostalCode))
{
query = query.Where(x => x.Addresses.Any(a => a.PostalCode.ToLower().StartsWith(searchCriteria.PostalCode)));
}
//other irrelevant filters
return query;
}
I'm trying to convert a DateTime value to a string, but I'm getting this runtime error:
Additional information: LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be
translated into a store expression.
This is the query I'm using:
using (var db = new DbContext())
{
var results = (from u in db.AspNetUserses
join upi in db.UserPersonalInfoes on u.Id equals upi.UserId into upis
from upi in upis.DefaultIfEmpty()
join up in db.UserPreferenceses on u.Id equals up.UserId into ups
from up in ups.DefaultIfEmpty()
join us in db.UserStatses on u.Id equals us.UserId into uss
from us in uss.DefaultIfEmpty()
select new
{
Username = u.UserName,
Telephone = (upi == null ? String.Empty : upi.Telephone),
ID = u.Id,
LastLogin = (us == null ? String.Empty : us.LastLoginDate)
}).ToList();
gvUsers.DataSource = results;
gvUsers.DataBind();
}
Any idea how can I fix this error?
Create a second list and do the transformation there.
using (var db = new DbContext())
{
var results = (from u in db.AspNetUserses
join upi in db.UserPersonalInfoes on u.Id equals upi.UserId into upis
from upi in upis.DefaultIfEmpty()
join up in db.UserPreferenceses on u.Id equals up.UserId into ups
from up in ups.DefaultIfEmpty()
join us in db.UserStatses on u.Id equals us.UserId into uss
from us in uss.DefaultIfEmpty()
select new
{
Username = u.UserName,
Telephone = (upi == null ? String.Empty : upi.Telephone),
ID = u.Id,
LastLogin = (us == null ? DateTime.MinValue : us.LastLoginDate)
}).ToList();
var list = (from r in results
select new {
Username = r.UserName,
Telephone = r.Telephone,
ID = r.ID
LastLogin = r.LastLogin == DateTime.MinValue ? "" : r.LastLogin.ToString()
}).ToList();
gvUsers.DataSource = list;
gvUsers.DataBind();
}
I am trying to convert this T-SQL to a Linq To SQL but can't work out the group by aggregate functions. Any help welcome.
select c.ClientID, GivenName, Surname, max(a.Address), max(t.Value)
from Client c
left join ClientAddress a on c.ClientID = a.ClientID
left join ClientContact t on c.ClientID = t.ClientID
group by c.ClientID, GivenName, Surname
To group by a composite key, you typically use an anonymous type:
var qry = from x in someSource
group x by new { x.ClientID, x.GivenName, x.Surname } into grp
select new { grp.Key, Address = grp.Max(x => x.Address),
Value = grp.Max(x => x.Value) };
The exact answer I came up with was
public IQueryable<ClientSearchDTO> GetClientsDTO()
{
return (from client in this.Context.Clients
join address in this.Context.ClientAddresses on client.ClientID equals address.ClientID
join contact in this.Context.ClientContacts on client.ClientID equals contact.ClientID
where contact.ContactType == "Phone"
group client by new { client.ClientID, client.Surname, client.GivenName } into clientGroup
select new ClientSearchDTO()
{
ClientID = clientGroup.Key.ClientID,
Surname = clientGroup.Key.Surname,
GivenName = clientGroup.Key.GivenName,
Address = clientGroup.Max(x => x.ClientAddresses.FirstOrDefault().Address),
PhoneNumber = clientGroup.Max(x => x.ClientContacts.FirstOrDefault().Value)
});
}