EF core - the most effective query in this case - c#

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();

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.

Entity Framework LINQ equivalent of TSQL to include count of child records

Using Entity Framework and LINQ, how might I achieve this TSQL:
SELECT Children.ChildCount, Parent.*
FROM Parent
LEFT JOIN (SELECT ParentID, COUNT(ChildID) AS ChildCount FROM Child GROUP BY ParentID) AS Children
ON Parent.ID = Children.ParentID
Note that this is just a small part of what is already a larger LINQ query that includes other related entities so using a RawSQL query is not an option. Also the Parent table has around 20 columns and I'm hoping not to have to specify each individually to keep the code maintainable.
EDIT: To clarify a couple of things, the model for the output of the query (very simplified) looks something like this:
public class MyEntity
{
public int ID {get; set;}
public string Name {get; set;}
public int ChildCount {get; set;}
// many other properties here including related records
}
So what I'm trying to do is get the ChildCount included in the result of the query so it is included in the EF entity.
You can use a Select Query to project the DB info onto an entity, something like:
var entity = db.Parent.Select(x =>
new MyEntity
{
Id = x.Id,
Name = x.Name,
ChildCount = x.Children
.Select(y => y.ParentId == x.Id)
.Count()
})
.SingleOrDefault(x => x.Id == IDYouNeedToQuery);
}
What this should do is return you 1 instance of your MyEntity class with the Name, ID, and ChildCount properties filled in. Your SQL won't quite match what is generated but this should get you what you want. BTW you can also sub the SingleOrDefault line with a filter of another type, or no filter in which case the entity variable becomes a collection of MyEntity.
For further reading on this technique and how to use AutoMapper to make it super easy to set up, check out this post from Jon P Smith, who literally wrote the book on Entity Framework Core.
Hope this helps.
For anyone who comes across this at a later date, I ended up using AutoMapper as suggested by Nik P (I was already using AutoMapper anyway) to map from db entities to DTOs.
In effect, in my AutoMapper mapping I have:
CreateMap<MyEntity, MyEntityDTO>()
.ForMember(d => d.ChildCount, opt => opt.MapFrom(src => src.ChildEntity.Count()))
Then in my Service layer I call:
IEnumerable<MyEntityDTO> results = await dbContext.MyEntities.ProjectTo<MyEntityDTO>(mapper.ConfigurationProvider).ToListAsync();

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();

Query a Many to Many to show certain Events

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.

Im trying to return related data in web api2 asp.net using DTO

public class hotelapiController : ApiController
{
private POSDBEntities db = new POSDBEntities();
public IList<HotelsDetailsDto> GetHotels()
{
return db.Hotels.Select(p => new HotelsDetailsDto
{
Id = p.Id,
Name = p.Name,
Address = p.Address,
Description = p.Description,
Offering = p.Offering,
Gps = p.Gps,
NumberOfRooms = p.NumberOfRooms,
Commission = p.Commission,
Rating = p.Rating,
HotelTypeName=p.HotelTypeName,
HimageId = p.HimageId, //HimageId is a foreign key
AveragePrice=p.AveragePrice,
BookingEmail =p.BookingEmail,
LocationId =p.LocationId, //LocationId is a foreign key
}).ToList();
}
When postman returns the JSON data it returns only the ids of the foreign key.
But I need it to return data in the location table ie Name and Image of the Location not only the Location Id. Im using data transer objects and Database First approach not code first. How do i go about i?
If you have correctly defined your foreign keys in the database, then (in my experience) Entity Framework should have identified this when you asked it to create the model from the database, and provided you with the linked entities within the model. e.g. your "Hotel" should have a "Location" property. So you should already be able to write something like:
public IList<HotelsDetailsDto> GetHotels()
{
return db.Hotels.Select(p => new HotelsDetailsDto
{
Id = p.Id,
//removed other lines for brevity
LocationName = p.Location.Name,
LocationImage = p.Location.Image
}).ToList();
}
If this is not the case, then define the database foreign keys between the tables correctly and then update your Entity Framework model. It should then provide you access to the entities you need.
It is unclear what EF version are you using, but it seems, that you want to eager load your Hotels object. You can do this by reading article provided or this stackoverflow answer Entity framework linq query Include() multiple children entities
And it should like this:
public IList<HotelsDetailsDto> GetHotels()
{
return db.Hotels.Include(x => x.Himage).Include(x => x.Location)
.Select(p => new HotelsDetailsDto
{
Id = p.Id,
Name = p.Name,
// Other fields
HimageId = p.Himage.Id, //HimageId is a foreign key
LocationId =p.Location.Id, //LocationId is a foreign key
}).ToList();

Categories

Resources