LINQ Grouping By with Left Join Results - c#

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

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;}
}

Using Parent and Child FK in Entity Framework

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.

Delete Multiple rows in several tables

I have 3 tables in an SQL database:
Animal Status
Animals
Cart
I want to be able to delete several items from the Animals and Animal Status tables based on the cart.
What's the same between all the tables is the AnimalID which is the primary key in all the tables and based on that I'm supposed to delete them. But at the same time I need to delete it for someone with a specific email.
Example is if I have someone with the email 123#gmail.com and this person has 2 animals in his cart 1 with the ID 123 and the other with the id 456
I need to delete these 2 specific animals from the other 2 tables.
I know how to do it for 1 table but not when I have several tables that depend on each other along with them depending on the specific email when there are also other emails and animals in the cart.
Here is how it works on the outside:
x= AnimalStatus.Delete(AdoptCart.GetAnimalId(Session["email"].ToString()).ToString());
if (x > 0)
{
x = Animal.Delete(AdoptCart.GetAnimalId(Session["email"].ToString()).ToString());
if (x > 0)
{
AdoptCart.RemoveAllbyEmail(Session["email"].ToString());
Here are all the deletion codes in the order they work:
Removes the AnimalStatus based on the animals ID:
static public int Delete(string id)
{
int rowsAffected;
string strSql = string.Format("delete from AnimalStatus where AnimalID='" + id + "'");
rowsAffected = (int)dataservice.ExecuteNonQuery(strSql);
return rowsAffected;
}
Removes an animal from the animal table based on the animal's ID:
static public int Delete(string id)
{
int rowsAffected;
string strSql = string.Format("delete from Animal where AnimalID='" + id + "'");
rowsAffected = (int)dataservice.ExecuteNonQuery(strSql);
return rowsAffected;
}
Removes all animals from the Cart Table:
static public int Remove(string AnimalID)
{
int rowsAffected;
string strSql = string.Format("delete from Cart where AnimalID='" + AnimalID + "'");
rowsAffected = (int)dataservice.ExecuteNonQuery(strSql);
return rowsAffected;
}
Issue with this code is that all of that only deletes 1 animal and I need several removed.
I have tried creating a statement like this, but I didn't manage to make it work.:
Delete Animal.*, AnimalStatus.*
FROM Animal INNER JOIN
AnimalStatus ON Animal.AnimalID = AnimalStatus.AnimalID INNER JOIN
Cart ON Animal.AnimalID = Cart.AnimalID
WHERE (Cart.UserEmail = N'Email')
Based on the example I gave in words the ones that are supposed to be deleted are the following marked in //removed.
Cart Table
| AnimalID | Email_Addr |
--------------------------------------
| 123 | 123#gmail.com | //removed
| 456 | 123#gmail.com | //removed
| 765 | jj#gmail.com |
| 343 | bb#gmail.com |
| 256 | cc#gmail.com |
Animal Status Table
| AnimalID | Vaccinated |
--------------------------------------
| 123 | Yes | //removed
| 456 | Yes | //removed
| 765 | No |
| 343 | No |
| 256 | No |
Animals Tables
| AnimalID | Age |
--------------------------------------
| 123 | 3 | //removed
| 456 | 4 | //removed
| 765 | 3 |
| 343 | 7 |
| 256 | 10|
Looking at your current work there are a few things you could do. You could rewrite your query logic like you're asking, but what I'm not seeing in your example is primary/foreign key relationships. So cascade won't work with how you currently have your tables set up. This is a good write up of cascade delete. Cascade Delete
So if you don't want to rewrite everything you really could just do all of this in your C# code. All you'd have to do is loop through the cart table and get all of the id's that have the e-mail "123#gmail.com" and call your three delete statements. You'd just need to write a method to grab the id's and call the delete methods until the id variable is empty.
It just depends where you want all of the logic to exist, in sql or in your C# code. If you're using Entity Framework, whether code first or database first, you have options to use LINQ Query syntax or extension methods. Just depends on preference really.

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

Selecting parents recursively (for ACL/Permissions system)

Important things
Model Category:
[Table("Category")]
public class Category
{
public Category()
{
Children = new HashSet<Category>();
}
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Column("Parent")]
public int? ParentId { get; set; }
[ForeignKey("ParentId")]
public virtual Category Parent { get; set; }
public virtual ICollection<Category> Children { get; set; }
public string Name { get; set; }
}
Database entries:
---------------------------------
| Id | Parent | Name |
---------------------------------
| 1 | NULL | Top Category |
---------------------------------
| 2 | 1 | Second Category |
---------------------------------
| 3 | 2 | Third Category |
---------------------------------
| 4 | 3 | Fifth Category |
---------------------------------
And now the problem...
How i use a query like...
SELECT "Name" FROM "Category" WHERE "Parent" IS NULL;
with a query like...
SELECT "c"."Id", "c"."Parent", "p"."Name" FROM "Category" "c" LEFT JOIN "Category" "p" ON "p"."Id" = "c"."Parent" WHERE "c"."Name" LIKE '%d%';
I want to select a category with all their parents recursively.
It would be really nice to have a LINQ query for that.
Expected result is something like this:
----------------------------------------------
| "c"."Id" | "c"."Parent" | "p"."Name" |
----------------------------------------------
| 2 | 1 | Top Category |
----------------------------------------------
| 3 | 2 | Second Category |
----------------------------------------------
Or with a group?
-----------------------------------------------------------
| "c"."Id" | "c"."Parent" | GROUP_CONCAT(...) |
-----------------------------------------------------------
| 2, 3 | 1, 2 | Top Category, Second Category |
-----------------------------------------------------------
The category "system" above is a example for a bigger plan...:
( Is there a framework / nuget plugin which allow to use ACL/Permissions/Rights on objects which stored in database?
I have users and groups. Then i have folders and files. Each user or group can have rights for specific folder or file. You can enable heredity for file oder folder-permissions. And with this query i try to read all permissions for a user and a specific folder with all their parents... )
Try this query
var result =
from first in db.Category
join second in db.Category on first.Id equals second.ParentId.GetValueOrDefault(0)
where first.name.Contains(parameter)
select new
{
Id = first.Id ,
ParentId= second.ParentId ?? 0,
ParentName= second.Name
};
I have used second.ParentId.GetValueOrDefault(0) because the join is made on two differents types int and nullable of int

Categories