Using Parent and Child FK in Entity Framework - c#

There are some situations using Foreign Key in a table that FK has Parent or Child relations with another table as shown below:
Staff:
Id | Name | CityId |
-------------------------
1001 | John | 1 |
1002 | Mary | 2 |
1003 | Bill | 3 |
1004 | Jose | 4 |
1005 | Anna | 5 |
City:
Id | Name | CountryId |
----------------------------
1 | NY | 101 |
2 | Paris | 102 |
3 | London | 103 |
4 | Rome | 104 |
5 | Tokyo | 105 |
Country:
Id | Name |
---------------
101 | USA |
102 | France |
103 | UK |
104 | Italy |
105 | Japan |
My question is that: Should we use only CityId as FK in the Staff entity, or is it better to include CityId and its parent CountryId in the Staff entity? Of course it seems to be redundant to include a FK and its parent as FK, but I wanted to be clarified if there is some situations or requirements when using such a king of relations with cascade? Which one should be used?

I would avoid breaking normalization because there is no simple way to enforce that a staff's Country reference and it's City's Country reference are guraranteed to be in sync. For instance, a Staff might have a City set to "Toronto" with a Country set to "Canada", but then somewhere the City is updated to "Boston", but Staff.Country is not updated. Staff says country is "Canada" while it's City says country is "USA". Who is trusted as the source of truth?
When it comes to displaying information about staff, use View Models to flatten out relevant details. If you want to display staff details with a Country name and nothing else about the city, the view model can expose that based on data selected from the entities. For instance:
var staffViewModels = context.Staff
.Select(x => new StaffViewModel
{
StaffId = x.Id,
Name = x.Name,
Country = x.City.Country.Name
}).ToList();
Even if the structure introduced an Address for staff which consisted of a City which related to a country:
var staffViewModels = context.Staff
.Select(x => new StaffViewModel
{
StaffId = x.Id,
Name = x.Name,
Country = x.Address.City.Country.Name
}).ToList();
The entity can remain normalized and generate the SQL to efficiently access the relevant data.

Related

How to Join or search Id with one column which have some Id

I have two tables:
Student Conference
+----+------+ +----+-------+---------------+
| id | name | | id | name | ref_studentId |
+----+------+ +----+-------+---------------+
| 1 | jack | | 1 | Rose | 1,12 |
| 2 | kai | | 2 | White | 12 |
| 3 | wind | | 3 | Black | 1,12,12356 |
+----+------+ +----+-------+---------------+
And I want to join them together with studentid or search for studentid in conference table.
You can use the LINQ Join method to achieve what you want:
var result = dataContext.Students.Join(dataContext.Conferences,
st => st.id,
cf => cf.ref_studentId,
(student, conference) => new { ... });
Although I would strongly recommend using the Entity Framework Navigation Properties, using which the above can be done more easily:
var result = dataContext.Student.Include(st => st.conference);
Update:
Please note that the above LINQ Query will fail to execute unless you will fix your Conference Table design, which is against SQL Normalization Forms (specifically the 1st Normal Form), saying that:
Each sell should be single-valued.
That means that you shouldn't have a comma (or any other character) separated values in your table columns.
To make the above query work, you have to make the ref_studentId contain only a single value for the StudentId:
Conference
+----+--------+-----------+
| ID | Name | StudentId |
+----+--------+-----------+
| 1 | Rose | 1 |
| 2 | Rose | 12 |
| 3 | White | 12 |
| 4 | Black | 1 |
| 5 | Black | 12 |
| 6 | Black | 12356 |
+----+--------+-----------+
But to be honest, this one too will not match the SQL Normalization rules.
Specifically, the 2nd Normal Form, saying that:
All Attributes (non-key columns) should be dependent on a Key.
And the 4th Normal form, saying that:
There should be no multi-value dependencies (Rose ⟶ 1, Rose ⟶ 12)
The correct solution would be to create another table for the Student⟶Conference relations.
Conference ConferencesStudents
+----+-------+ +----+--------------+-----------+
| ID | Name | | ID | ConferenceID | StudentId |
+----+-------+ +----+--------------+-----------+
| 1 | Rose | | 1 | 1 | 1 |
| 2 | White | | 2 | 1 | 12 |
| 3 | Black | | 3 | 2 | 12 |
+----+-------+ | 4 | 3 | 1 |
| 5 | 3 | 12 |
| 6 | 3 | 12356 |
+----+--------------+-----------+
Now, for this one the LINQ query will be:
dataContext.ConferencesStudents.Join(dataContext.Students,
cf => cf.ConferenceID,
st => st.ID,
(conferencesStudents, student) => new { conferencesStudents, student })
.Join(dataContext.Conferences,
cfSt => cfSt.conferencesStudents.ConferenceID,
cf => cf.ID,
(cfSt, conference) =>
new
{
Student = cfSt.student,
Conference = conference
});
Note: for the above, I've used the Anonymous Types just for demonstration and my strong advice would be to use real class Models instead.
OR
By using the same Navigation Properties (in case if you correctly had defined the EF relations) you can have a simpler version:
dataContext.ConferenceStudents.Include(cf => cf.Conferences)
.Include(cf => cf.Students)
Update 2:
I hate to say it but there is a kind of workaround in case if you can't change the Database design:
var results = (
from c in dataContext.Conference.ToList() // populating all Conferences
from s in dataContext.Students.ToList() // populating all Students
where c.ref_studentId.Split(',').Contains(s.id.ToString())
select new
{
Student = s,
Conference = c
}).ToList();
Note: this WILL NOT BE EFFECTIVE from the application performance perspective.
The better alternative compared to the above could be to write Stored Procedure and call it from the EF.
I have prepared a fiddle for you - if there are any questions feel free to ask.
Btw: This solution solves your question. The data-structure itself is ugly and should be normalized.
void Main()
{
var students = new List<Student>
{
new Student { Id = 1, Name = "jack" },
new Student { Id = 12, Name = "kai" },
new Student { Id = 12356, Name = "wind" }
};
var conferences = new List<Conference>
{
new Conference { Id = 1, Name = "Rose", RefIds = "1,12" },
new Conference { Id = 2, Name = "White", RefIds = "12" },
new Conference { Id = 25, Name = "Black", RefIds = "1,12,12356" }
};
var result = students.Select(s => new Tuple<int, Conference[]>(s.Id, conferences.Where(c => c.RefIds.Split(",").Contains(s.Id.ToString())).ToArray()));
}
// Define other methods, classes and namespaces here
public class Student
{
public int Id {get; set;}
public string Name {get;set;}
}
public class Conference
{
public int Id {get; set;}
public string Name {get; set;}
public string RefIds {get; set;}
}

LINQ-to-Entities query on Entity Framework

I'm having a hard time creating a LINQ-to-Entities query that would fulfill my requirement.
I have two tables: Booking and ProcessStatusLog.
Booking table:
PNNumber (PK)
AccountName
ProcessStatusLog table:
ID (PK)
PNNumber (FK)
InsuranceCode
Status
UpdatedOn
Here is the sample data for these tables:
Booking table
| PNNumber | Account Name |
+----------+----------------+
| 11111 | Boston Celtics |
| 22222 | Miami Heat |
| 33333 | LA Lakers |
ProcessStatusLog table:
| ID | PNNumber | InsuranceCode | Status | UpdatedOn |
+------+-----------+---------------+--------------+-------------+
| 1 | 11111 | null | NEW | 02/22/2020 |
| 2 | 11111 | FIRE | FOR REVIEW | 02/23/2020 |
| 3 | 22222 | null | NEW | 02/24/2020 |
| 4 | 22222 | MORTGAGE | FOR REVIEW | 02/25/2020 |
| 5 | 22222 | MORTGAGE | CORRECTION | 02/26/2020 |
| 6 | 22222 | FIRE | FOR REVIEW | 02/27/2020 |
| 7 | 33333 | null | NEW | 02/28/2020 |
| 8 | 22222 | FIRE | APPROVED | 02/29/2020 |
Now, I want to get a list of bookings per latest status.
For example: if I want to filter the bookings where the latest status is "CORRECTION", I will get the booking with PNNumber 22222.
If searching for "FOR REVIEW", I will just get the booking with PNNumber 11111.
If searching for "NEW", I will just get the booking with PNNumber 33333.
How can I write the EF query for this?
Thanks.
context.ProcessStatusLog.where(x=>x.Status == "FOR REVIEW")
.OrderByDescending(x => x.UpdatedOn)
.Take(1);
You can use Join() to achieve it.
var result = context.ProcessStatusLog.Join(context.Booking, s => s.PNNumber, b => b.PNNumber, (s, b) => new { booking = b, StatusLog = s).Where(BookingAndStatusLog => BookingAndStatusLog.StatusLog.Status == your_parameter)
.OrderByDescending(BookingAndStatusLog => BookingAndStatusLog.StatusLog.UpdatedOn)
.Take(1);
Try below Query.
context.ProcessStatusLog.where(x=>x.Status == "FOR REVIEW")
.OrderByDescending(x => x.UpdatedOn)
.ToList();
So far my understanding of your question, you can try-
var result= context.ProcessStatusLog.OrderByDescending(x => x.UpdatedOn)
.FirstOrDefault(x=>x.Status == "CORRECTION");
OR
var result= context.ProcessStatusLog.where(x=>x.Status == "CORRECTION")
.OrderByDescending(x => x.UpdatedOn)
.FirstOrDefault();
it's return a single object of your ProcessStatusLog class.
using (var ctx = new SchoolDBEntities())
{
var student = ctx.Students
.SqlQuery("Select top1 from ProcessStatusLog where Status like '%"+#APPROVED+"%' order by UpdatedOn", new SqlParameter("#APPROVE", yourinputparameter))
.FirstOrDefault();
}

LINQ Grouping By with Left Join Results

If I got this list
PersonPhone
------------------------------------------------------------
| **PersonId** |**Name** | **PhoneId** | **PhoneNumber** |
------------------------------------------------------------
| 1 | John Doe | 1 | 111-55-5855 |
------------------------------------------------------------
| 1 | John Doe | 2 | 111-55-5521 |
------------------------------------------------------------
| 2 | Mary Jane| 3 | 254-565-855 |
------------------------------------------------------------
| 3 | J. Watson| NULL| NULL|
------------------------------------------------------------
I need to mapping to this object:
public class PersonContactInfo {
public int Id { get; set; }
public string Name { get; set; }
public List<string> Phones { get; set; }
}
With LINQ, how can I get one row for each person, with his phones in a List and paging?
I already have a query which result is like the PersonPhone result set, but I don't know how to grouping by PersonId and then join all the PhoneNumbers to List, and paging. For example, if I want a page size of three, how to make a sql query to get John Doe, Mary Jane and J. Watson with their phones, if the actual query returns 4 rows?
Note: I'm not (and can't) using Entity Framework, what I'm doing is and sql query that populate a list of PersonPhone, just like EF does.
Applying a group by:
var query= PersonPhoneSet
.GroupBy(p=>new {p.PersonId, p.Name})
.Select(g=> new PersonContactInfo
{
Id=g.Key.PersonId,
Name=g.Key.Name,
Phones= g.Select(p=>p.PhoneNumber).ToList()
}
);

LINQ join tables with value if no match

I know that there are some examples but I could not apply them on my code. I am pretty new to Linq and SQL. I have two tables I want to join.
First Table:
--------------
| Id | Count |
--------------
| 1 | 10 |
--------------
| 2 | 4 |
--------------
Second Table:
--------------
| Id | Name |
--------------
| 1 | Tom |
--------------
| 2 | John |
--------------
| 3 | Nick |
--------------
| 4 | Max |
--------------
As you can see, the second table has more records than the first. My goal is to join them based on the Id. The problem is that after I have joined the tables it only displays the matching records, which is Id 1 and 2. Though I want to display every Id (from 1 to 4) and if there is no match in both tables, there should be a default value 0.
It should look like this:
----------------------
| Id | Name | Count |
----------------------
| 1 | Tom | 10 |
----------------------
| 2 | John | 4 |
----------------------
| 3 | Nick | 0 |
----------------------
| 4 | Max | 0 |
----------------------
So far I have got this code:
// first table
var listCount = entity.tblKundes.Where(x => x.Studio == 2)
.Select(x => new { x.Id, x.Name})
.GroupBy(x => x.Name).ToList();
// second table
var listBerater = entity.tblUsers.Where(x => x.Studio == 2)
.Select(x => new { x.Id, x.Name})
.ToList();
// This join should be edited so that it displays non matching records as well
var test = listCount.Join(
listBerater,
count => count.Key,
berater => berater.Id,
(count, berater) => new { listCount = count, listBerater = berater }
).ToList();
Edit:
var test2 = (from list in listCount
join berater in listBerater on list.Berater equals berater.Id into gj
from sublist in gj.DefaultIfEmpty()
select new { sublist.Id, sublist.Nachname, sublist.Vorname }).ToList();
There is a typical concept in every Structured Query Languages which is called "Left join". Left-Join means that you will have all rows of data from first table even there is no equivalent in the second one. "Inner-Join" is a little different and only looks for matched rows of data.
Here you can find enough and complete information about your issue.
Left Join

Inputing Value in sql after counting c#

I'm kind of new to this but I'll try to give as much detail as possible. This is my SQL table.
Customers
Customers_Id (PK) | First | Last | Address | Phone | Tech_Id (FK) |
-------------------+-------+-------+--------------+----------+--------------+
1 | Bob | Smith | 123 Fake St. | 3298492 | 1 |
2 | John | Man | 123 Noe St. | 2930482 | 1 |
3 | Tom | Lee | 123 Polk St. | 9308523 | 2 |
...
Tech
Tech_Id (PK) | First | Last | Phone | Customer_Count |
--------------+-------+-------+---------+----------------+
1 | Tim | Bo | 9384027 | |
2 | Andy | Wong | 9374927 | |
3 | Jack | Help | 2183847 | |
...
I'm trying to find the best way to count how many customer that each tech has either using SQL Query or C# coding. I was thinking of doing query with Count and then insert into the Customer_Count in Tech table.
I'm using visual studio 2012 and SQL is created locally in visual studio. Please help!
You can do it through SQL - using a GROUP BY clause to group the result by each tech. You can use the COUNT function to return the number of customers assigned to each tech. You can put this in an UPDATE statement to update the customer_count field in the tech table for each tech.
For example:
UPDATE t
SET t.customer_count = COUNT(c.customer_id)
FROM tech t
INNER JOIN customers c ON c.tech_id = t.tech_id
GROUP BY c.tech_id
Remove the Customer_Count column from the tech table. It's best not to make columns which duplicate available data unless there's a valid performance reason. If you want it displayed as such, then create a view:
SELECT t.tech_id,
t.first,
t.last,
t.phone,
(SELECT COUNT(c.customer_id)
FROM customers c
WHERE c.tech_id = t.tech_id) AS Customer_Count
FROM tech t
Now you can query it like a table, but you're not tasked with maintaining data which is always up to date through this view.
I prefre to using Sql ,but failed...Disappionted..
But I am sure that you can use Ado.net to do this job.
Here is C# demo code.
//query from db use C#
var allCus = new List<Customers>();
var gps = allCus.GroupBy(w => w.Tech_Id);
foreach(var gp in gps) {
var techId = gp.Key;
var cnt = gp.Count();
//update Tech set Customer_Count = cnt where Tech_Id = techId
}
Here is Sql version
update Tech set Customer_Count = (select IdAndCnt.cnt from (select Tech_Id,count (Tech_id) as cnt from Customers group by Tech_Id ) as IdAndCnt where Tech.Tech_Id = IdAndCnt.Tech_Id)
Holp it works.

Categories