Query many to many relation with lambda expression - c#

I'm using entity framework database first.
For one to many relation, in sql server, I use the following sql query(Status_ID is foreign key):
SELECT Products.*, Status.Title as Pstatus
FROM Products, Status
WHERE Products.Status_ID = Status.ID
In MVC I use the following code to retrieve the same data as above sql query and pass the list to the View:
Controller:
var products = oDB.Products.Include(m => m.Status)
.ToList();
// string test = products[0].Status.Title;
return View(products);
In the view I can access the desired data by the following code:
View:
#model List<myDB.Product>
...
#item.Status.Title // This works well for each item in the list
For MANY TO MANY RELATIONS WITH JUNCTION TABLE, this is my .edmx:
Now How could I retrieve list of products including related categories?
I need the list of products and pas it to the view as a list, and access each product's categories in the view.
My Classes (These classes are generated automatically):
public partial class Category
{
public int ID { get; set; }
public string Title { get; set; }
public virtual ICollection<Products_Categories> Products_Categories { get; set; }
}
public partial class Products_Categories
{
public int ID { get; set; }
public Nullable<int> Product_ID { get; set; }
public Nullable<int> Category_ID { get; set; }
public virtual Category Category { get; set; }
public virtual Product Product { get; set; }
}
public partial class Product
{
public int ID { get; set; }
public string Title { get; set; }
public virtual ICollection<Products_Categories> Products_Categories { get; set; }
}

var products = oDB.Products.Include(m => m.Product_Categories.Select(pc=>pc.Category))
.ToList();
// string test = products[0].Status.Title;
return View(products);
and in a view you could use it like this
#foreach(var item in model){
<h3>string.join(", ", item.Product_Categories.Select(pc=>pc.Category.Title))</h3>
}

You need to use something like this:
var products = oDB.Products.Include("Status")
.ToList();
// string test = products[0].Status.Title;
return View(products);

Related

EF6(not EF core) using Navigation property instead of Linq joins

I have two tables that I need to join and filter. Orders and Customers. I have generated these classes using EF Code First from DB.
Generated classes for the tables -
Orders
[Table("Orders")]
public partial class Orders
{
[Key]
[StringLength(17)]
public string OrderID { get; set; }
public int ShipToCustomerID { get; set; }
//Navigation Property
public Customer Customer { get; set; }
}
Customers
[Table("Customer")]
public partial class Customer
{
public int CustomerID { get; set; }
public string AccountNumber { get; set; }
//Navigation prop
public int ShipToCustomerID { get; set; } (not a part of the table, just attempting to get the navigation work)
public Orders Order { get; set; }
}
Method 1:
LINQ Joins
using (var context = new OrderDetailsGeneral1())
{
var data = (from p in context.Orders
join q in context.Customers
on p.ShipToCustomerID equals q.CustomerID
where p.OrderID == "7150615"
select new
{
OrderID = p.OrderID,
CustomerID = q.AccountNumber
}
);
var orders = data.ToList();
return Json(orders);
}
This works well and I get the following output -
[
{
"OrderID": "7150615",
"CustomerID": "23320347 "
}
]
Method 2:
I read that it's better to use navigation properties than using joins and that's why I was trying to do so, as per that I added the navigation properties to the classes above.
I tried a bunch of ways to link them together. One of them is the way mentioned here and I came across a bunch of errors.
It would try to map Customers.CustomerID to Orders.OrderID instead of Orders.ShipToCustomerID.
What's the best way to achieve this? I am having a hard time figuring out linking this foreign key (Customers.CustomerID) to a non primary/alternate key (Orders.ShipToCustomerID)
you have to fix your classes
[Table("Orders")]
public partial class Order
{
[Key]
[StringLength(17)]
public string OrderID { get; set; }
public int ShipToCustomerID { get; set; }
[ForeignKey(nameof(ShipToCustomerID))]
[InverseProperty("Orders")]
public virtual Customer Customer { get; set; }
}
[Table("Customer")]
public partial class Customer
{
[Key]
public int CustomerID { get; set; }
public string AccountNumber { get; set; }
[InverseProperty(nameof(Order.Customer))]
public virtual ICollection<Order> Orders { get; set; }
}

Query joined tables c# code first

I have these tables i have made in c# using code first approach.
Employee class:
public int id { get; set; }
public string name { get; set; }
Department class:
public int id { get; set; }
public string deptName { get; set; }
public IQueryable<Employee> { get; set; }
This generates a DepartmentID in my Employee table in my sql database. I cannot however access this field in c# as DepartmentID is not a field in the employee class/model.
My question is how do i access this variable. I wish to do some various joins etc but am struggling with this.
You can certainly expose the foreign key, but it is not necessarily needed. The beauty of EF is you don't need joins.
First I would clean up your classes:
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
// Exposed FK. By convention, EF know this is a FK.
// EF will add one if you omit it.
public int DepartmentID { get; set; }
// Navigation properties are how you access the related (joined) data
public virtual Department Department { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
Now you can query your data easily:
var employeeWithDepartment = context.Employee
.Include(e => e.Department)
.FirstOrDefault(e => e.ID = 123);
var employeeName = employeeWithDepartment.Name;
var departmentName = employeeWithDepartment.Department.Name;
... etc.
var departmentWithListOfEmployees = context.Departments
.Include(d => d.Employees)
.Where(d => d.Name == "Accounting")
.ToList();
... build table or something
foreach (var employee in departmentWithListOfEmployees.Employees)
{
<tr><td>#employee.ID</td><td>#employee.Name</td>
}
... close table

Linq join with Many to Many

I'm using MVC, C# and EntityFramework.
I've seen different solutions on Many to Many joins and after a lot of tinkering I got it to work in Linqpad. But when I try it in my solution I get an error because one of the tables isn't in my DBContext.
I have two visible tables and one hidden. Items, Recipes & RecipeItems.
All recipes are based on one item and use two or more items to be made.
So I want a list, IEnumerable or similar with the data from both Items and Recipes that specifies this recipe and then I want all the items needed to make the recipe.
The following query works in LinqPad
var t = from r in Recipes
join i in Items on r.ItemId equals i.Id
select new {FinalProduct = r.FinalProduct, Effect= i.Effect,
Description = r.Description, Ingredients = r.RecipeItems.Select(g => g.Item)};
When I do this in my solution I get the error since my DBContext only contains Recipe and Items but no RecipeItems. Entityframework handles this without me I guess.
I tried to make a DbSet<RecipeItems> without any luck. Any of you who have a suggestion of how I can move forward.
Item Class
public class Item
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Effect { get; set; }
public bool Published { get; set; }
public virtual ICollection<Recipe> Recipe { get; set; }
}
Recipe Class
public class Recipe
{
[Key]
public int Id { get; set; }
public int ItemId { get; set; }
[Display(Name = "Final Product")]
public string FinalProduct { get; set; }
public string Description { get; set; }
public RecipeGroup RecipeGroup { get; set; }
public bool Published { get; set; }
public virtual ICollection<Item> Ingredients { get; set; }
}
The ItemId in Recipe is to set the actual Item the Recipe will make.
Try adding this to your Recipe object:
public Recipe()
{
this.Ingredients = new HashSet<Item>();
}
This overrides the default constructor for the class and kind of gives EF a place that initializes the related objects.

Create a LINQ query that fetches data from two Entities

I want to get to the product categories in a MVC project and use them to create a menu. The model resides on a WCF project so I have instantiated it as follows:
ServiceReference1.WSClient client = new ServiceReference1.WSClient();
My Product model is like this:
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public string ProductImagePath { get; set; }
public string Specifications { get; set; }
public string Options { get; set; }
public double? UnitPrice { get; set; }
public int? CategoryId { get; set; }
public Category Category { get; set; }
public int Stock { get; set; }
}
My Category model is like this:
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string Despcription { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
I want to get the product categories like this:
public PartialViewResult Menu()
{
List<Product> products = client.GetAvailableProducts().ToList();
IEnumerable<string> categories = products
.Select(myproduct => myproduct.Category.CategoryName) // <- offending line
.Distinct()
.OrderBy(x => x);
return PartialView(categories);
}
The method GetAvailableProducts() works because I get a list of products so I know that the service is working. However, when run the application, I get a null reference exception at the Linq query(see offending line above).
It seems to me that categories has to be instantiated but then, how to construct the LINQ query so that the Category is also instantiated? Can anyone point out how to do it?
BR,
Gabriel
When you serialize the Products in the service you should use LoadWith to also serialize any linked entities. This is because the default is lazy loading and EF won't load the linked entities until accessed. When you serialize the Products, Category is not accessed. LoadWith will perform an eager load so that all data will be serialized.
Example:
public IEnumerable<Product> GetAvailableProducts()
{
var ctx = new ProductsContext();
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Product>(p => p.Category);
ctx.LoadOptions = dlo;
return ctx.Products.ToList();
}
Edit:
Guess it's too late in the afternoon. :(
LoadWith is used in conjuction with Linq to SQL.
With Entity Framework you should use Include instead:
public IEnumerable<Product> GetAvailableProducts()
{
var ctx = new ProductsContext();
return ctx.Products.Include("Category").ToList();
}
Disclaimer: Code not tested
You're pulling down the Products successfully but your service is not pulling down the related categories.
Thus, when you do myproduct.Category.CategoryName, Category is always null.
You need to tell the service to return the related categories.
It looks like your relationship is a 0 or 1 to many since CategoryId is an int?. If the Category is null, then you can't do Category.CategoryName. That's a null reference.
On your offending line, myproduct.Category, there is one or more null Category. You need to change
client.GetAvailableProducts().ToList();
to also pull down the Category that each product is composed of.
You should also guard for the inevitable null Category.

How to pass values with distinct foreign key to the view in asp.net mvc 3

I have a 1:m relationship in my code first classes. The orders instance of my view show values with unique OrderID. Since one Customer can have many orders, the foreign key CustID is not unique in this view.
public class Customers
{
public int CustomerID { get; set; }
[StringLength(50, ErrorMessage = "{0} cannot exceed {1} characters")]
public string LastName { get; set; }
public string Initials { get; set; }
public string email {get; set;}
public List<Orders> orders { get; set; }
}
Orders class:
public class Orders
{
[Key]
public int OrdersID { get; set; }
public int OrderName { get; set; }
public int? CustomerID { get; set; }
public virtual Customers customer { get; set; }
}
Below is my controller:
private custContext db = new custContext();
//
// GET: /cust/
public ViewResult Index()
{
var cust = db.orders.Include(c => c.customer).Take(10);
return View(cust.ToList());
}
How do I show only distinct Customers in my view with their OrderNames in the same view?
You should think about renaming your classes and attributes to conform to the naming conventions. Use singular names like public class Order ... instead of Orders. Use Id as the primary key name for all fields. Start all your attributes with capital letters (pascal case) This will help the framework understand your model.
To get the data you want, just use:
var cust = db.Customers.Include("orders").Take(10);
If you make the convention changes this would become:
var cust = db.Customers.Include("Orders").Take(10);
You would need to setup the DbSet in your data context but it looks like you already figured this out since you are using db.Orders. Anyway here is the code (with Customer converted to singular):
public DbSet<Customer> Customers { get; set; }
In View : Start with ...
#model IList<ProjectName.Customers>
If you want to bind multi record then
foreach(var c in Model)
{
#Html.DisplayFor(modelItem => c.OrderName)
}
Otherwise #model.Orders.OrderNames....
public ViewResult Index()
{
var cust = db.Customers.get().Select(x => new Customers
{
CustomerID = x.CustomerID,
OrderName = x.Orders.OrderName,
});
return View(cust.Take(10).ToList());
}
View:
#model IList<ProjectName.Customers>
foreach(var c in Model)
{
#Html.DisplayFor(modelItem => c.OrderName)
}

Categories