Query a Many to Many to show certain Events - c#

I have an application for Volunteers to apply for Ceremonies.
They look up a list of ceremonies that are available and they simply click join and the relationship is created.
However, I would like this ceremony to be removed from the list if they are part of it.
Here's my action in my controller:
public ActionResult ViewAvailableCeremonies()
{
string username = Membership.GetUser().UserName;
var getVolunteer = (from vol in db.Volunteers
where username == vol.Username
select vol).SingleOrDefault();
var ceremonies = (from a in getVolunteer.Appointments
where a.Fee != null
&& a.Slots != 0
//&& getVolunteer.Appointments.Any(c => a.)
select a).ToList();
return View(ceremonies);
}
I've been trying to think of how to go about this but my brain is totally fried. I know I need to query the list to check if they are there.
As always, thanks for the help

Looks like you are filtering your available cerimonies based on Users appointments.
First you have to search for all the cerimonies:
E.G.
var ceremonies = (from a in db.Cerimonies
select a).ToList();
So, you filter the cerimonies, based on users Appointments:
I'm assuming that you have an relationship between Ceremony and Appointment where the foreign key in Appointment class is CerimonyID and the primary key in Ceremony class is ID
E.G.
string username = Membership.GetUser().UserName;
var getVolunteer = (from vol in db.Volunteers
where username == vol.Username
select vol).SingleOrDefault();
var userAppointmentsIds = (from a in getVolunteer.Appointments
where a.Fee != null &&
a.Slots != 0
select a.CerimonyID).ToList();
var filteredCerimonies = ceremonies
.Where(c => !userAppointmentsIds.Contain(c.ID))
.ToList();

So in you application you have a Volunteer, hopefully identified by his unique Id, or if really necessarily by non-unique Username.
In this application you want to show a list of all Ceremonies that this Volunteer is not attending yet.
In your example code I see something quite different. I see Appointments (is an Appointment the same as a Ceremony?) Apparently there is a requirement not to use all Appointments, but something that have non-null Fees and non-zero Slots,
Leaving that apart I see that you access your database (or your tables) more than once, which is not really efficient.
For efficiency reasons: try to keep your LINQ statements IEnumerable / IQueryable as long as possible, only when you need the final result use ToList / ToDictionary / SingleOrDefault / FirstOrDefault / Any etc
Concatenation of LINQ statements is fast. It just changes the expression needed to enumerate the sequence. The slow part is when you start enumeration in your ToList / Any / etc.
Having said this. I'm not sure if you're using entity framework to access your database. If so, your answer is easy. See the end.
Solution without entity framework
So to identify your Volunteer you have a unique volunteerId, or if not available a (fairly) unique userName.
You also have a sequence of Volunteers and a sequence of Ceremonies (which are Appointments with non-null Fee and non-zero Slots.
There is a many-to-many relationship between Volunteers and Appointments: Every Volunteer can have zero or more Appointments; every Appointment is attended by zero or more Volunteers.
In databases this is implemented using a junction table, let's call this table VolunteersAppointments.
Okay, so we have properly defined your problem. Here are the classes:
class Volunteer
{
public int Id {get; set;}
public string UserName {get; set;}
...
}
class Appointment
{
public int Id {get; set}
public <someType1> Fee {get; set;}
public <someType2> Slots {get; set;}
...
}
// junction table
class VolunteersAppointments
{
public int UserId {get; set;}
public int AppointmentId {get; set;}
}
It might be that you've defined your classes differently, but you get the gist.
To get all Ceremonies the user with UserName is not attending yet:
string userName = ...
IQueryable<Volunteer> volunteers = <your table of all Volunteers>
IQueryable<Appointment> appointments = <your table of all appointments>
// ceremonies are appointments with non-null Fee and non-zero Slots:
var ceremonies = appointments
.Where(appointment => appointment.Fee != null && appointment.Slots != 0);
var volunteersWithUserName = volunteers
.Where (volunteer => volunteer.UserName == userName);
// if Volunteer.UserName unique, this will lead to a sequence with one element
// if not unique, consider selection by Id
Now to get all ceremonies that are not attended by this volunteer I first need to get the ceremonies that are attended by the volunteer. For this I need the junction table. Join Volunteers with junction table with Ceremonies.
Normally I would use LINQ method-syntax instead of LINQ query-syntax, because it has more functionality. However a join with three tables looks horrible in method syntax. For this I use query syntax:
var attendedCeremonies = from volunteer in volunteersWithUserName
join volap in VolunteersAppointments on volap.VolunteerId equals volunteer.Id
join ceremony in ceremonies on volap.CeremonyId equals ceremony.Id
select ceremony;
var notAttendedCeremonies = ceremonties.Except(attendedCeremonies);
In your application I assume that you don't need all properties of the Ceremony. You'll probably only want to show the name / description / Time / Location. You'll also need the Id to make a new Appointment.
var displayableCeremonies = notAttendedCeremonies.Select(ceremony => new
{
Id = ceremony.Id,
Name = ceremony.Name,
Description = ceremony.Description,
Location = ceremony.Location,
Time = ceremony.Time,
Duration = ceremony.Duration,
});
Note that, although I created a lot of separate LINQ queries, no query is executed yet, your tables are not accessed yet. The statements only changed the expression in the query. There is no big performance penalty when writing it like this instead of one big LINQ statement. The bonus is that it is readable and thus better testable and maintainable.
The following will finally do the query:
var desiredResult = displayableCeremonies.ToList();
Entity Framework method
When using entity framework you don't need to perform the joins yourself. If you use the ICollections, entity framework will understand that a triple table join is needed.
class Volunteer
{
public int Id {get; set;}
// every volunteer has zero or more Appointments (many-to-many)
public virtual ICollection<Appointment> Appointments {get; set;}
...
}
class Appointment
{
public int Id {get; set}
// every Appointment is attended by zero or more Volunteers (many-to-many)
public virtual ICollection<Volunteer> Volunteers {get; set;}
...
}
public MyDbContext : DbContext
{
public DbSet<Volunteer> Volunteers {get; set;}
public DbSet<Appointment> Appointments {get; set;}
}
Because I stuck to the Entity Framework Code First Naming Conventions, this is enough for entity framework to understand that I meant to design a many-to-many relationship. Entity Framework will create and maintain the junction table for me. I don't need this table to perform my queries.
using (var dbContext = new MyDbContext())
{
var ceremoniesNotAttendedByUser = dbContext.Appointments
// keep only the Ceremony appointments
// that are not attended by any Volunteer with UserName
.Where(appointment =>
// only ceremonies:
appointment.Fee != null && appointment.Slots != 0
// and not attended by any volunteer that has userName
// = sequence of UserNames of the volunteers that attend this appointment
// does not contain userName
&& !appointment.Volunteers.Select(volunteer => volunteer.UserName)
.Contains(userName))
// Fetch only the properties you want to display:
.Select(ceremony=> new
{
Id = ceremony.Id,
Name = ceremony.Name,
Description = ceremony.Description,
Location = ceremony.Location,
Time = ceremony.Time,
Duration = ceremony.Duration,
})
.ToList();
}
You see that I don't need the junction table. Entity Framework makes it thus more natural for me (An appointment is attended by zero or more volunteers) that I dared to show it to you in one statement.

Related

Is there any way to retrieve data based on multple where from multiple table

I have been trying for a while to do this in every possible way. I have a table name employees and other table name qualifications
Every employee has qualifications, but on initial basis qualification of every employee has not been filled into database.
I have tried VIA EF and LINQ and raw SqlConnection and SqlCommand as well but still not getting good results.
Employees WITH PHD is retrieved by
ViewData["lecturer_phd"] = _context.TblQualification
.Count(q => q.QualificationType == 4 &&
q.Employee.DesignationCode == 3);
and NON PHD should get back with
ViewData["lecturer_nphd"] = _context.TblEmployees
.Count(e => e.DesignationCode == 3 &&
e.EmployeeQualifications.Any(q => q.QualificationType != 4));
But this is not working and I am not familiar with LINQ as well but I tried that as well not any result.
The raw SQL query is using this code:
SqlConnection con = new SqlConnection(_context.Database.GetConnectionString());
DbCommand cmd = new SqlCommand("SELECT Count(*) FROM [DbBzuCC].[dbo].[tblEmployees] LEFT JOIN tblQualifications ON tblEmployees.Employee_Code = tblQualifications.Employee_Code AND tblQualifications.qualification_type != 4 WHERE tblEmployees.Designation_Code = 3",
con);
con.Open();
ViewData["lecturer_nphd"] = (int) cmd.ExecuteScalar();
con.Close();
But all in vain. Any help will be appreciated. I will be thankful for any help from community. Thanks in advance
Actually qualification_type was null where it was not entered for any employee so In sql query we need to check
ISNULL like this
SELECT COUNT(*)
FROM [DbBzuCC].[dbo].[tblEmployees]
Left join tblQualifications ON tblEmployees.Employee_Code = tblQualifications.Employee_Code
WHERE tblEmployees.Designation_Code = 3 AND ISNULL(tblQualifications.qualification_type,1) != 4
And in entity framework you can do it like this
_context.TblEmployees
.Count(e => e.DesignationCode == 3 && e.EmployeeQualifications.All(q => q.QualificationType != 4));
Thanks everyone for your precious time.
Regards,
So you have Employees and Qualifications, and a one-to-many relation: every Employee has zero or more Qualifications, every Qualification is the Qualification of exactly one Employee, namely the Employee that the foreign key EmployeeCode refers to.
If you have followed the entity framework conventions, you will have classes similar to:
class Employee
{
public int Id {get; set;}
public string Name {get; set;}
...
// Every Employee has zero or more Qualifications (one-to-many)
public virtual ICollection<Qualification> {get; set;}
}
class Qualification
{
public int Id {get; set;}
public string Description {get; set;}
...
// Every qualification is the qualification of exactly one Employee using foreign key
public int EmployeeId {get; set;}
public virtual Employee Employee {get; set;}
}
This would be enough for entity framework to detect the tables, the columns in the tables, and the relations between the tables using the primary keys and foreign keys.
In entity framework the columns of the tables are represented by the non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many)
The foreign key Qualification.EmployeeId is a real column, hence it it non-virtual. Property Qualfication.Employee represents the relation, hence it is virtual.
There is only need for attributes nor fluent API if you deviate from the conventions, like you did in your foreign key. In DbContext.OnModelCreating:
modelBuilder.Entity<Employee>().HasKey(employee => employee.EmployeeCode);
modelBuilder.Entity<Employee>().HasMany(employee => employee.Qualifications)
.WithRequired(qualification => qualification.Employee)
.HasForeignKey(qualification => qualification.EmployeeCode);
In words: every Employee has a primary key in property EmployeeCode. Every Employee has zero or more Qualifications in property Employee.Qualifications. Every Qualification has exactly one Employee in property Qualification.Employee, using foreign key Qualificatioin.EmployeeCode.
Back to your question
Requirement: count the number of qualifications with qualification_type != 4 for Employees that have Designation_Code == 3
Usig the virtual ICollection this is straightforward:
int qualificationTypeToIgnore == 4;
int employeeDesignationCode == 3;
var result = dbContext.Qualifications.Where(qualification =>
qualification.QualificationType != qualificationTypeToIgnore &&
qualification.Employee.DesignationCode == employeeDesignationCode)
.Count();
In words: from all Qualifications, keep only those Qualifications that have a QualificationType unequal to qualificationTypeToIgnore that also belongs to an Employee that has a DesingationCode equal to employeeDesignationCode. Count the number of remaining Qualifications.
Entity framework knows the one-to-many relation, and will do the property Join for you.
Some people don't like to use the virtual properties, they prefer to do the join themselves.
var eligibleEmployees = dbContext.Employees
.Where(employee => Employee.DesignationCode == employeeDesignationCode);
var eligibleQualifications = dbContext.Qualifications
.Where(qualification => qualification.QualificationType != qualificationTypeToIgnore);
var result = eligibleQualifications.Join(eligibleEmployees,
qualification => qualification.EmployeeCode, // from every Qualification take the foreign key
employee => employee.EmployeeCode, // from every Employee take the primary key
// parameter resultSelector: from every (qualification, employee) combination make one new
(qualification, employee) => new {qualification, employee})
.Count();
Since you will not be using the join result, you could simplify parameter resultSelector:
(qualification, employee) => 1
Or let entity framework do the join:
var result = eligibleQualifications.Where(qualification => eligibleEmployees
.Any(employee => employee.EmployeeCode == qualification.EmployeeCode))
.Count();
From all eligible qualifications, count the number of qualifications that have an EmployeeCode that is also an EmployeeCode in the eligible Employees.
IMHO the solution using the virtual properties is the most elegant one.

EF core - the most effective query in this case

Sorry for the illegibility, I have to use code anonymization
I use EF Core and have 4 database entities:
class A
{
public int AId {get; set;}
public ICollection<AB> ABs {get; set;}
}
class AB
{
public int ABId {get; set;}
public A A {get; set;}
public B B {get; set;}
}
class B
{
public int BId {get; set;}
public ICollection<AB> ABs {get; set;}
public ICollection<C> Cs {get; set;}
}
class C
{
public int CId {get; set;}
public int UId {get; set;}
public B B {get; set;}
}
I am looking for the most effective query. I get AId and UId. I would like to get A where AId.AId = AId with all C where C.UId = UId. If there is no C with UId for some B (in A->AB->B->C) I would like to return new C {B = B} for that B.
I wrote something like this and later operate on this object. But I think it is not effective, because I load all UQ from database
var result = context.QS
.Where(qs => qs.QSId == QSId)
.Include(qs => qs.QSQ)
.ThenInclude(qsq => qsq.Q)
.ThenInclude(q => q.UQ)
.FirstOrDefault();
When using entity framework always use Select to select items, and Select only the properties that you plan to use. Only use Include if you plan to update the fetched items.
A database management system is extremely optimized to select values, way better than your local process. The slower part of a database query is the transfer of the selected values from the DBMS to your local process. Hence it is wise to select only the properties that you actually plan to use.
For example. Suppose you have a schools database, with a straightforward one-to-many relation between Schools and Students: every School has zero or more Students, and every Student attends exactly one School, namely the School that foreign key SchoolId refers to.
Now if you query "School [10] with all its 2000 Students", you know that every Student of this School will have a foreign key SchoolId with a value of 10. If you would use Include, you would transfer this value 10 2000 times, while you already fetched the value, namely the primary key of School.
Another advantage of using Select is that you can decouple your database tables from the results. If you later will change your database table, for instance add a column, or rename it, then the users of your queries won't have to change as long as they don't need the value of the newly added column.
virtual ICollection
A side remark: shouldn't the properties that express the relations between your tables not be virtual?
In entity framework the columns of your tables are represented by non-virtual properties; the virtual properties represent the relations between the tables (one-to-many, many-to-many, ...).
Hence a foreign key is non-virtual; the object that the foreign key refers to should be declared virtual.
Back to your question
Requirement: Given a QSId and a UId, get all QSses where QSId.QSId = QSId, each QS with all their UQs where UQ.UId = UId, or a default UQ if there are no UQs.
There are two methods for this: user the ICollection, in which case entity framework will know
Method using the ICollections
This method works with full entity framework. Sometimes I hear that EF core doesn't support this. You know core better than I do, so maybe you should edit this part of the answer.
int QSId = ...
int UId = ...
var result = dbContext.QSses
.Where(qs => qs.Id == QSId)
.Select(qs => new
{
// Select only the QS properties that you plan to use:
Id = qs.Id, // not really needed, you already know that it equals QSId
Name = qs.Name,
...
UQs = qs.QSQs.SelectMany(qsq => qsq.UQs)
.Where(uq => uq.UId == Uid)
.Select(uq => new UQ
{
// Only the properties that you plan to use:
Id = uq.UqId,
Name = uq.Name,
...
})
.ToList(),
})
The problem is, that if your qs has no UQs at all, you want a default value. The smartest is to transfer the empty collection to your local process, and if you detect that it is empty, use a new default item.
Continuing the code above:
// Transfer to local process:
.AsEnumerable()
.Select(qs => new
{
Id = qs.Id
Name = qs.Name,
...
UQs = qs.UQs.Any() ? qs.UQs : new List<UQ>() {new UQ {...}},
});
Whenever possible I use the method with the virtual ICollections, instead of doing a GroupJoin yourself. IMHO it looks way more similar to your requirements.
Do the (Group-)Joins yourself
Some people don't trust that entity framework creates the correct (Group-)Joins, or they use a version of entity framework (or provider) that doesn't support using the virtual ICollections. In that case you'll have to do the GroupJoins yourself.
When going from one to many, use GroupJoin. When going from many-to-one use Join.
So To the junction table: GroupJoin, from the junction table to your UQ use Join.
var result = dbContext.QSses
.Where(qs => qs.Id == QSId)
.GroupJoin(dbContext.QSQs,
qs => qs.Id, // from every QS take the primary key
qsq => qsq.QSId, // from every QSQ take the foreign key to the QS
// parameter resultSelector: from every QS with its zero or more QSQs make one new
(qs, qsqs) => new
{
Id = qs.Id,
Name = qs.Name,
...
// To get the UQs belonging to these qsqs, do a Join
// not a GroupJoin, every qsq has exactly one UQ.
UQs = qs.QSQs.Join(dbContext.UQs.Where(uq => uq.UId == Uid),
qsq => qsq.UQId, // take the foreign key to the UQ
uq => uq.Id, // take the primary key
// parameter resultSelector, take the qsq with its matching uq
(qsq, uq) => new
{
Id = uq.UqId,
Name = uq.Name,
...
})
.ToList(),
})
.AsEnumerable()
.Select(... See above);
Is this what you need? I got only 1 data of TableC.
var tableA = db.TableA.Where(x => x.TableAid == 2)
.Include(x => x.TableAb)
.Include("TableAb.TableB")
.Include("TableAb.TableB.TableC")
.FirstOrDefault();

Converting Entity to DTO along with child entities

Trying to convert an entity object to local object so i can use it for further transformations.
Here is the code that i am using to convert the entity object;
IEnumerable<SystemArea> result = (from sa in CurrentContext.systemarea
select new SystemArea
{
SystemAreaId = sa.SystemAreaId,
SystemAreaCode = sa.SystemAreaCode,
SystemAreaType = sa.SystemAreaType,
SystemAreaDescription = sa.SystemAreaDescription,
SystemAreaCreatedDate = sa.SystemAreaCreatedDate,
SystemAreaUpdateDate = sa.SystemAreaUpdateDate,
SystemAreaStatus = sa.SystemAreaStatus,
Count = sa.systemareafunctionality.Count,
SystemAreaFunctionality = sa.systemareafunctionality.Select(e => new SystemAreaFunctionality { SystemAreaCode =e.SystemAreaCode })
}).ToList();
Here the count variable is to confirm whether there is any child data in it.
SystemAreaFunctionality is the child object that i am trying to convert here by using SELECT function but it is always blank collection. Rest data is getting assigned to parent object but the only thing missing here is the child table records. Where am i going wrong, please help!
Generated SQL :
SELECT
`Project3`.`C1`,
`Project3`.`SystemAreaId`,
`Project3`.`SystemAreaCode`,
`Project3`.`SystemAreaType`,
`Project3`.`SystemAreaDescription`,
`Project3`.`SystemAreaCreatedDate`,
`Project3`.`SystemAreaUpdateDate`,
`Project3`.`SystemAreaStatus`,
`Project3`.`C3` AS `C2`,
`Project3`.`C2` AS `C3`,
`Project3`.`SystemAreaCode1`
FROM (SELECT
`Project1`.`SystemAreaId`,
`Project1`.`SystemAreaCode`,
`Project1`.`SystemAreaType`,
`Project1`.`SystemAreaDescription`,
`Project1`.`SystemAreaCreatedDate`,
`Project1`.`SystemAreaUpdateDate`,
`Project1`.`SystemAreaStatus`,
1 AS `C1`,
`Project2`.`SystemAreaCode` AS `SystemAreaCode1`,
`Project2`.`C1` AS `C2`,
`Project1`.`C1` AS `C3`
FROM (SELECT
`Extent1`.`SystemAreaId`,
`Extent1`.`SystemAreaCode`,
`Extent1`.`SystemAreaType`,
`Extent1`.`SystemAreaDescription`,
`Extent1`.`SystemAreaCreatedDate`,
`Extent1`.`SystemAreaUpdateDate`,
`Extent1`.`SystemAreaStatus`,
(SELECT
COUNT(1) AS `A1`
FROM `systemareafunctionality` AS `Extent2`
WHERE `Extent1`.`SystemAreaCode` = `Extent2`.`SystemAreaCode`) AS `C1`
FROM `systemarea` AS `Extent1`) AS `Project1` LEFT OUTER JOIN (SELECT
`Extent3`.`SystemAreaCode`,
1 AS `C1`
FROM `systemareafunctionality` AS `Extent3`) AS `Project2` ON `Project1`.`SystemAreaCode` = `Project2`.`SystemAreaCode`) AS `Project3`
ORDER BY
`Project3`.`SystemAreaCode` ASC,
`Project3`.`C2` ASC
JSON output:
[{"SystemAreaId":1,"SystemAreaCode":"KIO","SystemAreaType":"KIOSK","SystemAreaDescription":"tasks
related to
receptionist","SystemAreaCreatedDate":"/Date(1543421018000)/","SystemAreaUpdateDate":"/Date(1543421018000)/","SystemAreaStatus":true,"SystemAreaFunctionality":[],"Count":1}]
PS : Please don't suggest automapper or extension methods. Thanks!
OPINION :
Took me two days to make MySQL(latest version) work with EF and trust me it was painstaking and on the contrary EF with MSSQL is so simple and easy to implement.
One thing i experienced is that Oracle is not interested in providing support for the free version of MySQL whatsoever, so they are being sloppy on the documentation of new version and are providing unstable .NET connectors.
ACTUAL ANSWER :
EF was behaving so weirdly, that it would only load the data in the child entity (SystemAreaFunctionality) only if i asked EF to load the child of the child entity (i.e. SystemAreaFunctionalityEmployeeRoleMapping which is child to SystemAreaFuncionality), which also means that i had to take unnecessary data.
So my link query looks like this :
var result = (from sa in CurrentContext.systemarea
select new SystemArea
{
SystemAreaId = sa.SystemAreaId,
SystemAreaType = sa.SystemAreaType,
Count = sa.systemareafunctionality.Count,
SystemAreaFunctionalities = sa.systemareafunctionality.Select(saf => new SystemAreaFunctionality
{
SystemAreaId = saf.SystemAreaId,
SystemAreaFunctionalityController = saf.SystemAreaFunctionalityController,
SystemAreaFunctionalityAction = saf.SystemAreaFunctionalityAction,
SystemAreaFunctionalityType = saf.SystemAreaFunctionalityType,
SystemAreaFunctionalityEmployeeRoleMappings = saf.systemareafunctionalityemployeerolemapping.Select(saferm => new SystemAreaFunctionalityEmployeeRoleMapping
{
SystemAreaFunctionalityEmployeeRoleMappingId = saferm.SystemAreaFunctionalityEmployeeRoleMappingId,
SystemAreaFunctionalityCreatedDate = saferm.SystemAreaFunctionalityCreatedDate
})
})
}).ToList();
ALTERNATIVELY :
Tried using the same linq query (posted in OP) with different database this time with PostgreSQL plus npgsql connector and surprisingly EF gives me exactly what i want with out extra baggage.
On top of that PostgreSQL gives better performance with EF than MySQL. So i presume that switching to PostgreSQL would be a better option.
PS : If you are deciding on open sources DBMS then please refer this before jumping in with MySQL :
https://www.youtube.com/watch?v=emgJtr9tIME
https://www.reddit.com/r/csharp/comments/40x3nx/which_orm_is_the_right_choice_for_mysql
Use this:
CurrentContext.systemarea.Include('systemareafunctionality')
or
IEnumerable<SystemArea> result = (from sa in CurrentContext.systemarea
join systemareafunctionality in CurrentContext.systemareafunctionality on sa.systemareafunctionalityID equals systemareafunctionality.ID
select new SystemArea
{
SystemAreaId = sa.SystemAreaId,
SystemAreaCode = sa.SystemAreaCode,
SystemAreaType = sa.SystemAreaType,
SystemAreaDescription = sa.SystemAreaDescription,
SystemAreaCreatedDate = sa.SystemAreaCreatedDate,
SystemAreaUpdateDate = sa.SystemAreaUpdateDate,
SystemAreaStatus = SystemAreaStatus,
Count = systemareafunctionality.Count,
SystemAreaFunctionality = systemareafunctionality.Select(e => new SystemAreaFunctionality { SystemAreaCode =e.SystemAreaCode })
}).ToList();
or
IEnumerable<SystemArea> result = (from sa in CurrentContext.systemarea
join systemareafunctionality in CurrentContext.systemareafunctionality on sa.systemareafunctionalityID equals systemareafunctionality.ID into item1 from subitem1 in item1.DefaultIfEmpty()
select new SystemArea
{
SystemAreaId = sa.SystemAreaId,
SystemAreaCode = sa.SystemAreaCode,
SystemAreaType = sa.SystemAreaType,
SystemAreaDescription = sa.SystemAreaDescription,
SystemAreaCreatedDate = sa.SystemAreaCreatedDate,
SystemAreaUpdateDate = sa.SystemAreaUpdateDate,
SystemAreaStatus = SystemAreaStatus,
Count = systemareafunctionality.Count,
SystemAreaFunctionality = systemareafunctionality.Select(e => new SystemAreaFunctionality { SystemAreaCode =e.SystemAreaCode })
}).ToList();
Apparently your CurrentContext is a Dbcontext with at least a table of SystemAreas and a table of SystemAreaFunctionalities.
It seems that every SystemArea has zero or more SystemAreaFunctionalities; every SystemAreaFunctionality belongs to exactly one SystemArea, a straightforward one-to-many relationship using a foreign key.
Note: it might be that you have a many-to-many relation, the answer will be similar
Alas you forgot to write your classes, so I'll give a shot:
class SystemArea
{
public int Id {get; set;}
... // other properties
// every SystemArea has zero or more SystemAreaFunctionalities (one-to-many)
public virtual ICollection<SystemAreaFunctionality> SystemAreaFunctionalities {get; set;}
}
class SystemAreaFunctionality
{
public int Id {get; set;}
... // other properties
// every SystemAreaFunctionality belongs to exactly one SystemArea, using foreign key
public int SystemAreaId {get; set;}
public virtual SystemArea SystemArea {get; set;}
}
In entity framework the columns of your tables are represented by non-virtual properties, the virtual properties represent the relationships between the tables. (one-to-many, many-to-many, ...)
for completeness:
class CurrentContext : DbContext
{
public DbSet<SystemArea> SystemAreas {get; set;}
public DbSet<SystemAreaFunctionality> SystemAreaFunctionalities {get; set;}
}
If people want items with their sub-items, like Schools with their Students, Customers with their Orders, etc. people tend to perform a (group)Join. However, when using entity framework joins are seldom necessary. Ise the ICollections instead. Entity framework knows the relationships and knows which (group)join to perform.
Regularly I see people use Include, but if you do that, you'll select the complete object, which is not very efficient. Suppose you have a SystemArea with Id = 10, and 1000 SystemAreaFunctionalities, you know that every SystemAreaFunctionality has a foreign key SystemAreaId with a value 10. Instead of sending this value only once as primary key of SystemArea, Include will also select all 1000 foreign keys with this value 10. What a waste of processing power!
When querying data, always use Select and select only the properties you actually plan to use. Only use Include if you plan to Update the included object.
You wrote:
SystemAreaFunctionality is the child object that i am trying to convert here...
It is not clear what you really want. Do you want a collection of all used SystemAreaCodes? Or do you really want a collection of new SystemAreaFunctionalities where only one field is filled: SystemAreaCode? Because of you use of singular property name, it seems you don't want a collection but only one item.
var result = currentContext.SystemAreas.Select(systemArea => new
{
Id = systemArea.Id,
Code = systemArea.Code,
...
// if you want a collection of SystemAreaFunctionalities
// where every SystemAreaFunctionality is filled with only SysemAreaCode
// do the following:
SystemAreaFunctionalities = systemArea.SystemAreaFunctionalities
.Select(systemAreaFunctionality => new SystemAreFunctionality
{
SystemAreaCode = systemAreaFunctionality.SystemAreaCode,
})
.ToList(), // DON'T FORGET THIS, OR YOU WON'T GET RESULTS!
})
.ToList()
}
I think the cause of your empty SystemAreaFunctionalities is because you forgot to do ToList().
Because you used ToList(), you automatically have the Count of the selected SystemAreaFunctionalities. There is no need to select this Count separately.
One of the slower parts of database queries is the transport of the selected data from the database management system to your local process. It is good practice to only select data you actually plan to use
You query is not very efficient because you select complete SystemAreaFunctionalities and fill only the SystemAreaCode. All other fields will be filled by default values. Apart from the slower query, you also give your callers the impression that they get properly filled SystemAreaFunctionalities. An improved version would be:
var result = currentContext.SystemAreas.Select(systemArea => new
{
// select only the properties you actually plan to use
Id = systemArea.Id,
Code = systemArea.Code,
...
// if you only need the SystemAreaCodes, select only that property
SystemAreaCodes = systemArea.SystemAreaFunctionalities
.Select(systemAreaFunctionality => systemAreaFunctionality.SystemAreaCode)
.ToList(),
})
.ToList()
};
Of courds, if you need mrre than only the SystemAreaCodes, but several SystemAreaFunctionalities, go ahead, select them:
...
SystemAreaFunctionalities = systemArea.SystemAreaFunctionalities
.Select(systemAreaFunctionality => new
{
// again: select only the properties you plan to use!
Id = systemAreaFunctionality.Id
SystemAreaCode = systemAreaFunctionality.SystemAreaCode,
})
An include should do the trick for you like so:
IEnumerable<SystemArea> result = (from sa in CurrentContext.systemarea.Include("systemareafunctionality")
select new SystemArea
{
SystemAreaId = sa.SystemAreaId,
SystemAreaCode = sa.SystemAreaCode,
SystemAreaType = sa.SystemAreaType,
SystemAreaDescription = sa.SystemAreaDescription,
SystemAreaCreatedDate = sa.SystemAreaCreatedDate,
SystemAreaUpdateDate = sa.SystemAreaUpdateDate,
SystemAreaStatus = sa.SystemAreaStatus,
Count = sa.systemareafunctionality.Count,
SystemAreaFunctionality = sa.systemareafunctionality.Select(e => new SystemAreaFunctionality { SystemAreaCode =e.SystemAreaCode })
}).ToList();

Which LINQ query to select rows from 1 table that are not in another table

I'm developing an application in which I have 2 different entities, Products, and ShoppingCarts. Each product is unique and has a unique identifier. I want to add a product that is not already in another cart and that is not sold to a ShoppingCart.
Product entity simplified:
public class Products
{
public int Id { get; set; }
public string Name{ get; set; }
public bool Sold { get; set; }
}
Shopping Cart entity simplified:
public class ShoppingCarts
{
public int Guid Guid { get; set; }
public int ProductId { get; set; }
}
So first I retrieve all the Product.Id and then I add them to my cart. My method looks like this:
private IQueryable<Products> GetAvailableProductId(int quantity)
{
var query = (from p in _context.Set<Products>()
join sc in _context.Set<ShoppingCarts>() on p.Id equals sc.ProductId into subset
from sc in subset.DefaultIfEmpty()
where !p.Sold && sc == null
select p).Take(quantity);
return query;
}
For some reason, every once in a while, 2 entities with the same ProductId are being added to different carts. This was enabling the application to sell 2 of the same products. I ended up fixing this by performing another check in the application before I make the transaction.
I revisited the code recently and came across these posts LINQ Query: Determining if object in one list exists in another based on key
LINQ to Entity, joining on NOT IN tables
My question is if changing my query to something like this will prevent the double addition.
private IQueryable<Products> NewGetAvailableProductId(int quantity)
{
var query = (from p in _context.Set<Products>()
where !_context.Set<ShoppingCarts>().Any(x => x.ProductId == p.Id) && !p.Sold
select p).Take(quantity);
return query;
}
If there are any doubts, please let me know so I can explain this better.
Thanks,
Getting the distinct records from your original query should get you the desired result. Note the Distinct() before Take().
var query = (from p in _context.Set<Products>()
join sc in _context.Set<ShoppingCarts>() on p.Id equals sc.ProductId into subset
from sc in subset.DefaultIfEmpty()
where !p.Sold && sc == null
select p).Distinct().Take(quantity);
The reason you got duplicates is that the original query will give you a list of the matches between the product table and the cart table. for example, if you have product1 in cart1 and cart2 and a product2 in no carts you will get the following results from the join.
product1, cart1
product1, cart2
product2, null
you then filter out the null carts
product1, cart1
product1, cart2
you then select only the product object
product1
product1
At this point you are left with the duplicate products. The distinct function I added will then take this list and remove all but one of the duplicate entries. leaving you with,
product1
It is worth checking the sql generated by each of the queries as they could be quite different even though they produce similar results. I would suspect that your first query will use a LEFT OUTER JOIN while the second one will use an IN clause. I would use the LEFT OUTER JOIN as in my experience IN clauses are fairly slow and should be avoided if possible. Obviously you should measure this in your own environment.
Also, your second query is missing the where !p.Sold that was in the first one.
I have a feeling you're unintentionally barking up the wrong tree.
Here's the nutshell scenario:
User 1 wants to buy the product. The app checks whether it's in any cart. No.
User 2 wants to buy the product. The app checks whether it's in any cart. No.
User 1's thread completes the process of adding it to their cart.
User 2's thread completes the process of adding it to their cart. It assumes that, since the prior check succeeded, it's still safe to do so.
Basically, you need a transaction, critical section, singleton, or some similar device to ensure that one and only one person can check and add it to their cart as a single operation - it must succeed or fail as a single unit of work.
Please check out this question: LINQ to Entity, joining on NOT IN tables. Much cleaner approach than the above solutions.
Looking at your query, there is nothing to keep repeated records from showing up. You need to use this: How do I use Linq to obtain a unique list of properties from a list of objects?

Query common contacts (self many to many relationship) in entity framework

I have this classic scenario where I have a User table and a Contact table containing only UserId and ContactId columns (so it is a self many to many relationshsip). What I would like is a query that gives me a list of userIds with number of common contacts with the specified User. In plain old SQL I have the following query (contacts of user and user itself is filtered out to get facebook like friend suggestions):
SELECT COUNT(c1.ContactId) as CommonContact, c2.UserId
from Contacts as c1
inner join Contacts as c2 on c1.ContactId = c2.ContactId
Where c1.UserId = #Id AND c2.UserId != #Id
AND c2.UserId NOT IN (SELECT ContactId from Contacts Where UserId = #Id)
Group By c2.UserId
ORDER BY CommonContact Desc
This simple query works great but I can not figure out how to write the same query in LINQ to Entity, because in the Entity Framework model I have User entity that entity have Contact navigation property but the connection table is not there directly....
Thanks a lot for any help...
Didn't have time and try to run it but something like this should work.
public class Test
{
//simulate an IQueryable
private readonly IQueryable<Person> _people = new List<Person>().AsQueryable();
public void FindContactMatchCount(Guid personId)
{
//we'll need the list of id's of the users contacts for comparison, we don't need to resolve this yet though so
//we'll leave it as an IQueryable and not turn it into a collection
IQueryable<Guid> idsOfContacts = _people.Where(x => x.Id == personId).SelectMany(x => x.Contacts.Select(v => v.Id));
//find all the people who have a contact id that matches the selected users list of contact id's
//then project the results, this anonymous projection has two properties, the person and the contact count
var usersWithMatches = _people
.Where(x => idsOfContacts.Contains(x.Id))
.Select(z => new
{
Person = z, //this is the person record from the database, we'll need to extract display information
SharedContactCount = z.Contacts.Count(v => idsOfContacts.Contains(v.Id)) //
}).OrderBy(z => z.SharedContactCount)
.ToList();
}
}

Categories

Resources