Linq to Sql : how to get data - one to many - c#

My app in ASP.NET C#. I use Linq to Sql, look the picuture
// 5 data talbe :
// Unit table
id | name | unit_type_id Unit_type
-------------------------
1 | Unit 1 | 1 id | name
2 | Unit 2 | 1 -------------
3 | Unit 3 | 1 1 | Type 1
4 | Unit 4 | 2 2 | Type 2
5 | Unit 5 | 1 3 | Type 3
6 | Unit 6 | 3
//Tool table // tool_type table
id | name | tool_type_id id | name
---------------------------- -------------------
1 | Tool 1 | 1 1 | Tool_type 1
2 | Tool 2 | 2 2 | Tool_type 2
3 | Tool 3 | 1 3 | Tool_type 3
4 | Tool 4 | 2
5 | Tool 5 | 3
//unit_tool table
unit_id | tool_id
---------------
1 | 1
2 | 1
1 | 2
1 | 3
2 | 2
2 | 3
3 | 3
3 | 2
I want to use linq to sql in C# to get data like
// Table data i want to get
Unit name | Unit type name | Tool name
-------------------------------------------------
Unit 1 | Type 1 | tool 1, tool 2, tool 3
Unit 2 | Type 1 | tool 1, tool 2, tool 3
Unit 3 | Type 1 | tool 2, tool 3
Unit 4 | Type 2 |
Unit 5 | Type 1 |
Unit 6 | Type 3 |
My code be low
// Create new class
public class unit_list
{
//Unit name
public string unit_name { get; set; }
// Unit type name
public string unit_type_name { get; set; }
// Tool name
public string tool_name { get; set; }
}
// Function get data
protected void show_unit()
{
// Create datacontext
for_testDataContext db = new for_testDataContext();
//Query data
IQueryable<unit_list> CourseList = from cl in db.units
select new unit_list()
{
unit_name = cl.name,
unit_type_name = cl.unit_type.name,
tool_name = // how to get data like "tool1, tool 2, tool 3"
};
}

You should be able to write something like this (assuming all the relationships are set up correctly in Linq):
select new unit_list()
{
unit_name = cl.name,
unit_type_name = cl.unit_type.name,
tool_name = string.Join(", ",
cl.unit_tools.Select(ut => ut.tool.name).ToArray()
)
};
In other words..
Get the one-to-many relationship to unit_tools
Select the corresponding row in tool and select the name column
Join to get a single comma-separated string

You don't have any foreign keys set up, which means you don't get automatic "navigation properties" with your mapped entities. You will have to either update your database and then regenerate the mapping, or add them yourself using the toolbox.
EDIT:
Okay, let's run down a very short checklist.
Are primary and foreign keys set and associated properly in the schema (your database)? This is important, as this is what the
sqlmetal executable uses to generate your object context.
Do the associations in your model point to the correct properties in the parent and child entities? You can check this by clicking on
an association between two objects in the model viewer and then expanding the Properties
pane. Expand the Child and Parent properties and verify they are not
empty.
If you've done everything right so far, clicking on the association between Unit_Tool and Unit should show a Child property called Units.
If you're missing an association, you can add one by:
Clicking on the Association tool in the Toolbox.
Clicking on the object which defines the primary key.
Clicking on the object that holds the foreign key.
This will bring up a dialog box that should be pretty self-explanatory.
HTH.
EDIT:
Solved this by playing around with your existing code (thanks for the upload). The problem was super simple, but not exactly obvious: you need to have an identifier column in each table, or Linq-to-SQL doesn't create the child collection reference in the data context. So, since you're using unit_tool as just a pivot table (perfectly acceptable), without a unique ID on each row, you don't get a "navigation property." To fix this, just add a primary key field (you don't ever have to actually use it - it's just for the ORM) to the unit_tool table, recreate the entity mapping, and viola!, your query will work as-is.
I have to say, this wasn't obvious at first blush. I haven't ever had this problem, since I always create a unique identifier out of habit...

Related

Ordering by col1 but if null order by col2 in Entity Framework

I am currently trying to do something like this with EF Core (fictional example):
-- Tables:
Entity1
Id |
UserId (nullable) |
GroupId (nullable) |
Group
Id | UserId
User
Id | Name
-- Sample Data:
Entity1
Id | UserId | GroupId
1 | null | 1
2 | 3 | null
3 | null | 2
User
Id | Name
1 | John
2 | Jimmy
3 | Jessica
Group
*Id | UserId *
1 | 1
2 | 2
The entity can either have a user or a group. Depending on which case, I want to sort for the user.name (if it has a user order directly for entity.user.name otherwise order by entity.group.user.name. When Ordering by user.name I want the results to be interleaved, so it would feel like, user is never null in Enitity1.
query.OrderBy(x => (x.User??x.Group.User).Name)
I wished this would order by the User.Name if an user exists on the entity and if not it would order by the group.name of the entity.
Expected results
I would expect that the Ids of Entity1 would be ordered like 2,3,1 (--> Jessica, Jimmy, John)
I found this: Order by Column1 if Column1 is not null, otherwise order by Column2
But I wasn't sure how to convert that to EF Core. I know I can do what I want when I do it with LINQ in memory, but I was wondering, if there is a chance to execute this OrderBy on the db.

Get details by joining tables with group by condition in Linq and EF

I have the following 3 table definitions in my SQL Server database:
+----------------------------------+-----------------+------------------------------------------------
| Product | Rating | Image |
+----------------------------------+-----------------+-------------------------------------------------
| ProductId | Id | Id |
| ProdctName | Rating | Image |
| | ProductId FK References(Product)| ProductId FK References(Product)|
+----------------------------------+-----------------+---------------+----------------------------------
And these tables contain the following sample data:
+----------------------------------+------------
|Product | ProductId |ProductName |
+----------------------------------+------------
| | 1 |Prodcuct 1 |
| | 2 |Prodcuct 2 |
| | 3 |Prodcuct 3 |
| | 4 |Prodcuct 4 |
+----------------------------------+------------+
+----------------------------------+----------------------+
|Rating | Id |RatingVal |ProductId |
|+----------------------------------+-----------------------
| | 1 |3 |1 |
| | 2 |2 |2 |
| | 3 |3 |2 |
| | 4 |5 |3 |
| | 5 |4 |3 |
+---------------------+------------+------------+----------+
+----------------------------------+----------------------+
|Image | Id |ImagePath |ProductId
+----------------------------------+-----------------------
| | 1 |ABC |1 |
| | 2 |XYZ |2 |
| | 3 |LMN |3 |
| | 4 |PQR |4 |
+---------------------+------------+------------+----------+
I need to gather information about a product in one place, such that each product contains the details about product ( from products table), related average rating ( from ratings table)m and the image path for the product ( from Image table). In other words I need the following output:
+----------------------------------+--------------------------------+
|Output | ProductId |ProductName |Avg(Rating)|ImgPath|
+----------------------------------+--------------------------------+
| | 1 |Prodcuct 1 |3 |ABC |
| | 2 |Prodcuct 2 |2.5 |XYZ |
| | 3 |Prodcuct 3 |4.5 |LMN |
| | 4 |Prodcuct 4 |0.0 |PQR |
+----------------------------------+------------+-----------+-------+
I am using Entity Framework to fetch this data, and entities in context class in my code( shown below).
My question is: How do I produce my desired output for all the products.
My code below is not able to get all the data I want. The problem is that the product with id4 is not shown in the result, I assume this is be cause product 4 does not have an entry in the ratings table.
var trendingProducts = (from ratings in entities.Rating
group ratings by new { ratings.productId } into c
join products in entities.Products on c.FirstOrDefault().productId equals products.ProductID
join images in entities.Images on c.Key.productId equals images.ProductId
select new ProductViewModel
{
ProductId = products.ProductId,
ProductName = products.ProductName,
RatingVal = c.Average(l => l.RatingVal) == null ? 0 : c.Average(l => l.Rating),
ImagePath = images.ImagePath,
}).ToList();
So you have a table of Products, a table of Ratings and a table of Images.
Every Product has zero or more Ratings, every Rating belongs to exactly one Product using foreign key ProductId. Similarly, every Product has zero or more Images, every Image belongs to exactly one Image, using foreign key ProductId. Just standard one-to-many relations.
It might be that every Product has zero-or-one Image: in that case you have a zero-or-one-to-one relation. The code will be similar. The main difference is that 'Product' won't have a collection of Images, it will only have one virtual Image.
If you follow the entity framework code first conventions you'll have classes like:
class Product
{
public int Id {get; set;}
// every Product has zero or more Ratings:
public virtual ICollection<Rating> Ratings {get; set;}
// every product has zero or more Images:
public virtual ICollection<Image> Images {get; set;}
... // other properties
}
class Rating
{
public int Id {get; set;}
// every Rating belongs to exactly one Product using foreign key:
public int ProductId {get; set;}
public virtual Product Product {get; set;}
...
}
class Image
{
public int Id {get; set;}
// every Image belongs to exactly one Product using foreign key:
public int ProductId {get; set;}
public virtual Product Product {get; set;}
...
}
This is everything entity framework needs to know to detect your one-to-many relations. It knows which tables / columns you want, and it knows the relations between the tables. It might be that you want different identifiers for your tables or properties. In that case you'll need attributes or fluent API. But that is out of scope of this question.
Note that in entity framework all non-virtual properties will become columns in your tables. All virtual properties represent the relations between the tables.
I need to gather information about a product in one place, such that
each product contains the details about product ( from products
table), related average rating ( from ratings table) and the image
path for the product (from Image table).
Whenever people query for "objects with their sub-object" they tend to create a (group)join. However, if you use entity framework it is much easier to use the ICollections for these queries. If you use an ICollection, Entity Framework will know that a (group)join is needed.
var result = myDbContext.Products // from the collection of Products
.Where(product => ...) // select only those Products that ...
.Select(product => new // from every remaining Product make one new
{ // object with the following properties:
// Select only the properties you actually plan to use!
Id = product.Id,
Name = product.ProductName,
...
AverageRating = product.Ratings // from all Ratings of this Product
.Select(rating => rating.Rating) // get the Rating value
.Average(), // and Average it.
Images = product.Images // from all Images of this product
.Select(image => image.ImagePath) // select the ImagePath
.ToList(),
// or if a Product has only zero or one Image:
Image = product.Image?.ImagePath // if the product has an Image, get its ImagePath
// or use null if there is no Image
});
The nice thing about using the ICollections is that the code is simplere and more natural, it looks more similar to the text of your requirement than a (group)join. Besides, if more than two tables are involved (group)joins tend to look horrible.
I won't try to give a solution using a GroupJoin. I'm sure other answers will show this. Compare and see for yourself which solution seems easier to understand.

How do you create a vertical stack org Chart

I need to build a Org Chart that prints from a web page and I have something like 19 to 32 entities on the second level and 3-7 entities on the third level.
I tried a solution called OrgChart component from Team Improver at
http://www.orgchartcomponent.com/default.aspx
but they are in England and don't respond to their email.
I also tried a solution from
https://www.codeproject.com/Articles/18378/Organization-Chart-Generator
that has the basic tree org chart.
I populate from a database via Ling to SQL, No Problem.
Parent Entity - Level 1
|
|
---Child Entity Level 2
|
|
---Child Entity Level 2
|
|
---Child Entity Level 2
|
|
---Child Entity Level 2
|
|
---Child Entity Level 2
|
|
---Child Entity Level 2
|
|
Child Entity Level 3
|
|
Child Entity Level 3
|
|
Child Entity Level 3
|
|
Child Entity Level 3
// using recursive function.
static void Get_Geneology(Employee manager, int Level = 0) // find all genealogy (kid to great grandkids or employee under a manager down the pyramid)
{
string indentLines = new string(' ', Level * 2);
var managedEmployees = Employees.Where(e => e.Manager_ID == manager.Emp_ID); // 1st call. look all employees under a Manager. or all kids of a father.
Console.WriteLine($"{indentLines} {Level + 1}-{manager.FirstName}{(managedEmployees.Count() > 0 ? "*" : "")}"); // with * after name mean that person manage sombody or father have kid(s)
foreach (var employee in managedEmployees) Get_Geneology(employee, Level + 1); // recursive, find all under under (to the deapest of each people geneology for each manager)
}

Linq except return whole table

I have a table of items using linq to entities, say:
ID | Name | Attb1 | Attb2
1 | Apple | Green | Juicy
2 | Orange | Orange | sweet
etc
I have another list with just ID's in it.
Using Linq I want to return all fields from the item table except where the ID is in the list. i.e. if my list just has '1' in it I want to return 2 | orange | orange | sweet
You could try something like this
var result = table.Where(x => !list.Contains(x.id));
where I have assumed that table holds all the rows of your table and list contains the ids that you want to exclude.

how to structure data that will be fed into dropdown boxes?

I have denormalized data:
+----+----------+------+--------+
| pk | name | type | animal |
+----+----------+------+--------+
| 1 | alex | car | cat |
| 2 | alex | bike | cat |
| 3 | liza | car | dog |
| 4 | danielle | bike | dog |
| 5 | danielle | bus | dog |
+----+----------+------+--------+
I would like to have 3 dropdown boxes.
name
type
animal
after the user chooses the option for the first, there should be a cascade effect for the other dropdowns.
example: if the user chooses danielle for the name, the only two options for type would be bike and bus and the only option for animal would be dog
How should I structure the SQL tables? Should I denormalize?
I'd say that your solution depends on how much data do you have in this table.
If this table is relatively small, you could load it into memory, fill comboboxes by distinct values and then filter data by chosen field.
If it large, you maybe should denormalize your table as #astander says, fill comboboxes with data from reference tables and then when value changes, select filters from SQL like:
declare #name_id int -- input parameter, fill it with id of chosen name
-- filter for type combo
select distinct type_id from main_table where name_id = #name_id
-- filter for animal combo
select distinct animal_id from main_table where name_id = #name_id

Categories