This is my query, it works fine and gives me expected result;
var result2 = (from dt in disMudahaleTipiRepo
join dm in disMudahaleRepo on dt.Kodu equals dm.MudahaleKodu
join kr in kurumRepo on dm.CreatedKurumKodu equals kr.KurumKodu
join yu in userRepo on dm.CreatedBy equals yu.ID
group dt by new { yu.Ad, yu.Soyad, kr.KurumAdi } into grp
select new
{
AdSoyAd = grp.Key.Ad + " " + grp.Key.Soyad,
KurumAdi = grp.Key.KurumAdi,
KoduSayisi1 = grp.Select(s => s.Kodu).Where(w=>w== "401.030").Count(),
KoduSayisi2 = grp.Select(s => s.Kodu).Where(w=>w== "402.090").Count(),
KoduSayisi3 = grp.Select(s => s.Kodu).Where(w=>w== "406.020").Count(),
KoduSayisi4 = grp.Select(s => s.Kodu).Where(w=>w== "402.020").Count(),
KoduSayisi5 = grp.Select(s => s.Kodu).Where(w=>w== "406.070").Count()
...
});
only problem is there are 86 fields more(lol), so it will end up like that:
...
select new
{
AdSoyAd = grp.Key.Ad + " " + grp.Key.Soyad,
KurumAdi = grp.Key.KurumAdi,
KoduSayisi1 = grp.Select(s => s.Kodu).Where(w=>w== "401.030").Count(),
...
KoduSayisi91 = grp.Select(s => s.Kodu).Where(w=>w== "436.070").Count()
});
Instead of doing this, I tried to create a dictionary and keep as (Code, Number) its good in theory but how can I tell this to linq?
I don't think definition of entities is necessary here but let me share;
public class DisMudahaleTipi : Entity
{
public string Kodu { get; set; }
public string Name { get; set; }
}
public class DisMudahale : AuditableEntity
{
public string MudahaleKodu { get; set; }
}
You could try to use a subquery, but I don't have a database at hand to test if the following works:
var keys = ["401.300", .. ];
var result = (from dt in disMudahaleTipiRepo
join dm in disMudahaleRepo on dt.Kodu equals dm.MudahaleKodu
join kr in kurumRepo on dm.CreatedKurumKodu equals kr.KurumKodu
join yu in userRepo on dm.CreatedBy equals yu.ID
group dt by new { yu.Ad, yu.Soyad, kr.KurumAdi } into grp
select new
{
AdSoyAd = grp.Key.Ad + " " + grp.Key.Soyad,
KurumAdi = grp.Key.KurumAdi,
Kudos = (from key in keys
select new
{
Key = key,
Amount = grp.Count(w => w.Kudo == key)
}).ToList(),
}).ToList();
Note: In addition to the subquery I changed Select(s=>s.Kudo).Where(w=>w=="...").Count() to reduce Linq complexity and improve performance.
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 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 have 3 tables structured in the following way:
CREATE TABLE [User](
Id int NOT NULL,
Name varchar(50)
PRIMARY KEY (Id)
)
CREATE TABLE [Role](
Id int NOT NULL,
UserId int NOT NULL,
Name varchar(50),
PRIMARY KEY (Id),
FOREIGN KEY (UserId) REFERENCES [User](Id)
)
CREATE TABLE [Description](
Id int NOT NULL,
RoleId int NOT NULL,
Name varchar(50)
FOREIGN KEY (RoleId) REFERENCES [Role](Id)
)
As you can see it is one to many relationship nested twice. In the code I have the following classes to represent them:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Role> Roles { get; set; }
}
public class Role
{
public int Id { get; set; }
public int UserId { get; set; }
public string Name { get; set; }
public IEnumerable<Description> Descriptions { get; set; }
}
public class Description
{
public int Id { get; set; }
public int RoleId { get; set; }
public string Name { get; set; }
}
Now I need to query for the user and also get all fields that come with it. I have figured out a way to do that using QueryMultiple such as this:
var queryOne = "SELECT Id, Name FROM [User] WHERE Id = 1";
var queryTwo = "SELECT r.Id, r.UserId, r.Name FROM [User] u INNER JOIN [Role] r ON u.Id = r.UserId WHERE u.Id = 1";
var queryThree = "SELECT d.Id, d.RoleId, d.Name FROM [User] u INNER JOIN [Role] r ON u.Id = r.UserId INNER JOIN [Description] d ON r.Id = d.RoleId WHERE u.Id = 1";
var conn = new SqlConnection();
using (var con = conn)
{
var result = con.QueryMultiple(queryOne + " " + queryTwo + " " + queryThree);
var users = result.Read<User>().FirstOrDefault();
var roles = result.Read<Role>();
var descriptions = result.Read<Description>();
if (users != null && roles != null)
{
users.Roles = roles;
Console.WriteLine("User: " + users.Name);
foreach (var role in users.Roles)
{
Console.WriteLine("Role: " + role.Name);
if (descriptions != null)
{
role.Descriptions = descriptions.Where(d => d.RoleId == role.Id);
foreach (var roleDescription in role.Descriptions)
{
Console.WriteLine("Description: " + roleDescription.Name);
}
}
}
}
}
The result is:
User: Bob
Role: Tester
Description: Tester First Description
Description: Tester Second Description
Description: Tester Third Description
Role: Manager
Description: Manager First Description
Description: Manager Second Description
Description: Manager Third Description
Role: Programmer
Description: Programmer First Description
Description: Programmer Second Description
Description: Programmer Third Description
Main Question:
While the above code works it feels too messy. I was wondering if there is a better/easier way to achieve this?
Bonus Points:
Please also feel free to suggest a better way to query this than using inner joins. My goal is to improve performance.
EDIT:
I came up with option two as well, but again I don't think its a good solution. With option 2 I create a 4th object that will contain results of the 3 objects combined such as this:
public class Combination
{
public int UserId { get; set; }
public string UserName { get; set; }
public int RoleId { get; set; }
public string RoleName { get; set; }
public int DescriptionId { get; set; }
public string DescriptionName { get; set; }
}
Then I process it like this:
var queryFour = "SELECT u.Id as 'UserId', u.Name as 'UserName', r.Id as 'RoleId', r.Name as 'RoleName', d.Id as 'DescriptionId', d.Name as 'DescriptionName' FROM [User] u INNER JOIN [Role] r ON u.Id = r.UserId INNER JOIN [Description] d ON r.Id = d.RoleId WHERE u.Id = 1";
var conn = new SqlConnection();
using (var con = conn)
{
var myUser = new User();
var result = con.Query<Combination>(queryFour);
if (result != null)
{
var user = result.FirstOrDefault();
myUser.Id = user.UserId;
myUser.Name = user.UserName;
var roles = result.GroupBy(x => x.RoleId).Select(x => x.FirstOrDefault());
var myRoles = new List<Role>();
if (roles != null)
{
foreach (var role in roles)
{
var myRole = new Role
{
Id = role.RoleId,
Name = role.RoleName
};
var descriptions = result.Where(x => x.RoleId == myRole.Id);
var descList = new List<Description>();
foreach (var description in descriptions)
{
var desc = new Description
{
Id = description.DescriptionId,
RoleId = description.RoleId,
Name = description.DescriptionName
};
descList.Add(desc);
}
myRole.Descriptions = descList;
myRoles.Add(myRole);
}
}
myUser.Roles = myRoles;
}
Console.WriteLine("User: " + myUser.Name);
foreach (var myUserRole in myUser.Roles)
{
Console.WriteLine("Role: " + myUserRole.Name);
foreach (var description in myUserRole.Descriptions)
{
Console.WriteLine("Description: " + description.Name);
}
}
}
The resulting output is the same in both methods and the second method uses 1 query as opposed to 3.
EDIT 2: Something to consider, my data for these 3 tables are updated often.
EDIT 3:
private static void SqlTest()
{
using (IDbConnection connection = new SqlConnection())
{
var queryOne = "SELECT Id FROM [TestTable] With(nolock) WHERE Id = 1";
var queryTwo = "SELECT B.Id, B.TestTableId FROM [TestTable] A With(nolock) INNER JOIN [TestTable2] B With(nolock) ON A.Id = B.TestTableId WHERE A.Id = 1";
var queryThree = "SELECT C.Id, C.TestTable2Id FROM [TestTable3] C With(nolock) INNER JOIN [TestTable2] B With(nolock) ON B.Id = C.TestTable2Id INNER JOIN [TestTable] A With(nolock) ON A.Id = B.TestTableId WHERE A.Id = 1";
var gridReader = connection.QueryMultiple(queryOne + " " + queryTwo + " " + queryThree);
var user = gridReader.Read<Class1>().FirstOrDefault();
var roles = gridReader.Read<Class2>().ToList();
var descriptions = gridReader.Read<Class3>().ToLookup(d => d.Id);
user.Roles= roles;
user.Roles.ForEach(r => r.Properties = descriptions[r.Id].ToList());
}
}
Your first option can be simplified to the following. It removes the deep control structure nesting. You can generalize it to deeper nesting without adding more nesting/complexity.
var queryOne = "SELECT Id, Name FROM [User] WHERE Id = 1";
var queryTwo = "SELECT r.Id, r.UserId, r.Name FROM [User] u INNER JOIN [Role] r ON u.Id = r.UserId WHERE u.Id = 1";
var queryThree = "SELECT d.Id, d.RoleId, d.Name FROM [User] u INNER JOIN [Role] r ON u.Id = r.UserId INNER JOIN [Description] d ON r.Id = d.RoleId WHERE u.Id = 1";
var conn = new SqlConnection();
using (var con = conn)
{
var gridReader = con.QueryMultiple(queryOne + " " + queryTwo + " " + queryThree);
var user = gridReader.Read<User>().FirstOrDefault();
if (user == null)
{
return;
}
var roles = gridReader.Read<Role>().ToList();
var descriptions = gridReader.Read<Description>().ToLookup(d => d.RoleId);
user.Roles = roles;
roles.ForEach(r => r.Descriptions = descriptions[r.Id]);
}
Performance-wise it behaves the same as your first option.
I would't go with your second option (or the similar view based one): in case you have R roles, and on average D descriptions per roles, you would query 6*R*D cells instead of 2+3*R+3*D. If R and D are high, you'd be querying a lot more data, and the cost of deserialization will be to high compared to running 3 queries instead of 1.
An alternative approach (Working code snippet with .Net Core):
Why not create a database View like this -
create view myview
As
SELECT u.Id as 'UserId', u.Name as 'UserName', r.Id as 'RoleId',
r.Name as 'RoleName', d.Id as 'DescriptionId', d.Name as 'DescriptionName'
FROM User u INNER JOIN Role r ON u.Id = r.UserId
INNER JOIN Description d ON r.Id = d.RoleId;
Then you can use your cleaner query as below :
public List<Combination> GetData(int userId)
{
String query = "select * from myview" + " where userId = " + userId + ";";
using (System.Data.Common.DbConnection _Connection = database.Connection)
{
_Connection.Open();
return _Connection.Query<Combination>(query).ToList();
}
}
And your processing code will look like this :
[Note: This can be enhanced even further.]
public static void process (List<Combination> list)
{
User myUser = new User(); myUser.Id = list[0].UserId; myUser.Name = list[0].UserName;
var myroles = new List<Role>(); var r = new Role(); string currentRole = list[0].RoleName;
var descList = new List<Description>(); var d = new Description();
// All stuff done in a single loop.
foreach (var v in list)
{
d = new Description() { Id = v.DescriptionId, RoleId = v.RoleId, Name = v.DescriptionName };
if (currentRole == v.RoleName)
{
r = new Role() { Id = v.RoleId, Name = v.RoleName, UserId = v.UserId, Descriptions = descList };
descList.Add(d);
}
else
{
myroles.Add(r);
descList = new List<Description>(); descList.Add(d);
currentRole = v.RoleName;
}
}
myroles.Add(r);
myUser.Roles = myroles;
Console.WriteLine("User: " + myUser.Name);
foreach (var myUserRole in myUser.Roles)
{
Console.WriteLine("Role: " + myUserRole.Name);
foreach (var description in myUserRole.Descriptions)
{
Console.WriteLine("Description: " + description.Name);
}
}
}
From the performance standpoint, Inner Joins are better than other forms of query like subquery, Co-related subquery, etc. But ultimately everything boils down to the SQL execution plan.
The tl;dr of my answer to you:
Abstract. You should break-up things into the different jobs they do. C# gives you functions and classes, they can help clean up code a lot.
If you can look at a chunk of code and say "this chunk is basically taking something and doing blah with it to get some result..", then make a function called SomeResult DoBlah(SomeThing thing)
Don't let a little more code scare you, it may make the big picture seem more complex at first glance. But it will make small chunks of code way easier to understand, and that makes the big picture easier to understand.
My full answer:
There are a couple of things you could do that would make this cleaner. Probably the biggest thing you could do for yourself, is to separate out some of the responsibilities/concerns. You have a lot of different things going on, and all of it is in one place, relying on everything staying exactly like it is, forever. If you change something, you will have to change things all over the place to make sure everything works. This is called "coupling", and coupling is bad... um-kay.
At the broadest level, you have three different things going on: 1. You are trying to apply logic to users and roles (i.e. You want to get info on all of the users in a role.) 2. You are trying to pull info from a database, and fit it into User, Role, and Description objects. 3. You are trying to display information about Users, Roles, and Descriptions.
So if I were you, I would have at least 3 classes right there. \
Program that is the driver of your application. It should have a main function and should use the other 3 classes.
DisplayManager that can be responsible for writing info out to a console. It should be constructed with an IEnumerable<User> and should have a public method public void DisplayInfo() that will write its list of users to a console.
UserDataAccess that can be used for fetching a list of users from a database. I would have it expose a public method for public IEnumerable<User> GetUsersByRoleID(int roleID)
This way, your main program would look something like this:
public static void Main(String[] args)
{
int roleID = 6;
UserDataAccess dataAccess = new UserDataAccess();
IEnumerable<User> usersInRole = dataAccess.GetUsersByRoleID(roleID);
DisplayManager displayManager = new DisplayManager(usersInRole);
displayManager.DisplayInfo();
}
Honestly, this is a simplified version of what I would do if the problem had more functionality then just getting a single specific piece of info. You should look into SOLID principles. Specifically "Single Responsibility" and "Dependency Inversion", these principles can really clean up some "messy"/"smelly" code.
Next, as far as your actually data access goes, since you are using dapper, you should be able to map nested objects using build in dapper functionality. I believe this link should be of some help to you in this aspect.
I have three database model which are shown below
I have two DTO class which are shown below
class RoleDTO
{
string RoleId;
string EnglishName;
Guid TypeId;
List<ClaimDTO> claims;
}
class ClaimDTO
{
string ActionID;
string ActionCode;
string ActionLevel;
string GrantDate;
}
Now I want to retrieve List of RoleDTO object from the database. So far I tried
public List<RoleDTO> GetRoleByType(Guid roleTypeId)
{
var roleDTOs = (from r in ctx.Roles
join rc in ctx.RoleClaims on r.RoleID equals rc.RoleID
join a in ctx.Actions on rc.ActionID equals a.ActionID
where r.RoleTypeID == roleTypeId
select new RoleDTO
{
RoleId = r.RoleID,
EnglishName = r.EnglishName,
TypeId = r.TypeID,
claims = List of ClaimDTO objects related to this role
}).ToList();
return roleDTOs;
}
My question is how can I retrieve list of ClaimDTO objects inside select statement. Is my linq correct?
I am using Telerik OpenAccess as ORM.
Below change should help to get the results
public List<RoleDTO> GetRoleByType(Guid roleTypeId)
{
var roleDTOs = (from r in ctx.Roles
join rc in ctx.RoleClaims on r.RoleID equals rc.RoleID
where r.RoleTypeID == roleTypeId
select new RoleDTO
{
RoleId = r.RoleID,
EnglishName = r.EnglishName,
TypeId = r.TypeID,
claims = ctx.Actions.Where( c => c.ActionId == rc.ActionId).Select( s => new ClaimDTO
{
ActionID = s.ActionID,
ActionCode = s.ActionCode,
ActionLevel = s.ActionLevel,
GrantDate = s.GrantDate
})ToList()
}).ToList();
return roleDTOs;
}
Another Alternative
List<ClaimDTO> claimsList = ctx.Actions.Select( s => new ClaimDTO
{
ActionID = s.ActionID,
ActionCode = s.ActionCode,
ActionLevel = s.ActionLevel,
GrantDate = s.GrantDate
})ToList();
var roleDTOs = ctx.Roles.Join(ctx.RoleClaims, r => r.RoleID, rc => rc.RoleID, (r,rc) => new
{
r,rc
}).Where( r => r.RoleTypeID == roleTypeId)
.Select( row => new RoleDTO
{
RoleID = row.r.RoleID,
EnglishName = row.r.EnglishName,
TypeID = row.r.TypeID,
claims = claimsList.Where( c => c.ActionId == rc.ActionId)
}).ToList();
If you use Include method then below query could help
var roleDTOs = ctx.Roles.Include("RoleClaims").Join(ctx.Actions, r => r.RoleClaims.Select(rc => rc.ActionID).FirstOrDefault() , a => a.actionid , (r,a) => new
{
r,a
}.Where(r => r.RoleTypeID == roleTypeId)
.Select( row => new RoleDTO
{
RoleID = row.r.RoleID,
EnglishName = row.r.EnglishName,
TypeId = row.r.RoleTypeID,
Claims = row.a.Select( c => new ClaimDTO
{
ActionID = c.ActionID,
ActionCode = c.ActionCode,
ActionLevel = c.ActionLabel,
GrantDate = row.r.RoleClaims.Select( g => g.grantDate)
})
}).ToList();
Just experimenting with Linq:
DataClassesDataContext db = new DataClassesDataContext();
var q = from p in db.tblBlogEntries select p;
foreach (var customer in q)
{
Response.Write(customer.ID + " " + customer.title + "\n");
}
Works fine, but I can only seem to return 1 field, or all of them. How do I select say, p.ID and p.title, and nothing else?
you need a projection to an anonymous type to only return the "parts" that you want:
var q = from p in db.tblBlogEntries select new { p.ID, p.Title };
Alternatively you could also define a new class that only has the subset of the properties you want. This might be beneficial if you want to return instances of the projection, since anonymous types can only be used in local scope:
var q = from p in db.tblBlogEntries
select new BlogTitleAndId() { ID = p.ID, Title = p.Title };
Instead of "select p"
Use:
var q = from p in db.tblBlogEntries select new { Column1 = p.Column1, Column2 = p.Column2 };
The new{} makes a inline class, so you can select whichever columns you need.
Something like this...
DataClassesDataContext db = new DataClassesDataContext();
var q = from p in db.tblBlogEntries select new { Id = p.ID, Title = p.title };
foreach (var customer in q)
{
Response.Write(customer.ID + " " + customer.title + "\n");
}
var q = from p in db.tblBlogEntries select new {p.ID, p.Title}
How about this:
DataClassesDataContext db = new DataClassesDataContext();
var q = from p in db.tblBlogEntries select new { ID = p.ID, title = p.title };
foreach (var customer in q)
{
Response.Write(customer.ID + " " + customer.title + "\n");
}
This part new { ID = p.ID, title = p.title } creates an anonymous class with members ID and title. You can add members for any column you wish and the types will be deduced automatically. Getting all the fields isn't that big a deal though. This article provides more information and a sample similar t yours.