Navigate across relationships in EF - c#

I'm begginer with EF, so my question is probably basic, but I couldn't find any answer...
I have a SQL Compact DB, from which I generated an entity model via the VS wizard. Everything seems fine, I retrieve all my relationships with the good mapping.
So as I understand from here: http://msdn.microsoft.com/en-us/library/bb386932(v=vs.110).aspx I should be able to do this, "querying across relationships":
IQueryable<Ingredient> IngQuery = from i in db.Ingredient
where i.Product.ID == ProdID
select i;
But I get the following error:
'System.Collections.Generic.ICollection' does not
contain a definition for 'ID' and no extension method 'ID' accepting a
first argument of type
'System.Collections.Generic.ICollection' could be found
(are you missing a using directive or an assembly reference?).
This error occurs when you try to call a method or access a class member that does not exist
However, if I go deeper into the auto-generated code, I can see a public property 'ID' is declared for 'Product', and 'Ingredient' return a collection of 'Product':
Ingredient
public partial class Ingredient
{
public Ingredient()
{
this.Product = new HashSet<Product>();
}
public string Name { get; set; }
public int ID { get; set; }
public virtual ICollection<Product> Product { get; set; }
}
Product
public partial class Products
{
public Products()
{
this.Ingredient = new HashSet<T_PROPSTHERAP>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Usage { get; set; }
public byte[] Photo { get; set; }
public int FK_OrganeProduct { get; set; }
public int FK_Type { get; set; }
public virtual OrganeProduct OrganeProduct { get; set; }
public virtual Type Type { get; set; }
public virtual ICollection<Ingredient> Ingredient { get; set; }
}
But it doesn't work as I expected.
I can use the following as workaround:
List<Ingredient> lstIng = (_oTest.Products
.Where(p => p.Name == (string)lsbProducts.SelectedItem)
.SelectMany(p => p.T_PROPSTHERAP)).ToList();
But I don't think it's a smart way to do the trick... And I don't understand what I am missing...
Could anyone help?

If I understand you correctly, you are trying to find Ingredients based on Product's ID. As you have known, the Product property is a collection, not a singular object.
What you need is filtering Products based on Product's ID, you can use Any to filter collection.
IQueryable<Ingredient> IngQuery = from i in db.Ingredient
where i.Product.Any(p => p.ID == ProdID)
select i;
That means:
Looking for Ingredient if any of its product has ID equals to ProdID.
You can also use All, if what you are looking for is:
Looking for Ingredient if all of its products have ID equals to ProdID.
IQueryable<Ingredient> IngQuery = from i in db.Ingredient
where i.Product.All(p => p.ID == ProdID)
select i;
PS
However, based on your workaround, using Any is what you are looking for.

Related

How to add an object multiple times in different tables with Entity Framework Core [Tracking error]

To simplify, I have a "Project" type that can contain several "Manager" and several "Employee". Each Employee and Manager is composed of an "Id" and "Person" property.
public class Project
{
...
public ObservableCollection<Manager> Managers { get; set; }
public ObservableCollection<Employee> Employees { get; set; }
}
public class Manager
{
public int Id { get; set; }
public Person Person { get; set; }
}
public class Employee
{
public int Id { get; set; }
public Person Person { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
I retrieve the information about a project from the database in the following way:
Project project = context.Set<Project>()
.Include(p => proj.Managers).ThenInclude(d => d.Person)
.Include(p => proj.Employees).ThenInclude(e => e.Person)
.FirstOrDefault(p => p.Id == id);
When I want to modify the Employees participating in the project I select the employee from a list of Persons
List<Person> Persons = context.Set<Person>().AsNoTracking().ToList();
...
theCurrentProject.Employees.Add(new Employee{ Person = theSelectedPerson});
Then when I call context.SaveChanges();, if the Person is already present in the list of Managers and I also add it to the Employees the following error occurs: The instance of type 'Person' cannot be tracked because another instance with the key value '{Id:x}' is already being tracked.
I'm doing something wrong but I can't see what. I understand that at the time of loading the information from the database the different "Persons" present in the project are "Tracked" by EF and therefore the person that comes from the list of Persons is not the same as the one already beeing tracked. That's why I tried to add AsNoTracking() but without success. "Persons" are read-only in this software (I don't need the tracking on them). How can I make this work ? And also possibly how to tell EF that such or such object is read-only and that it should not try to modify (track) them?
I have tried 4-5 different approaches but none of them solves my problem.
thanks in advance !
Try this:
var employee=new Employee{ PersonId = theSelectedPerson.Id};
context.Set<Employees>.Add(employee);
context.SaveChanges();
// get saved employee
var savedEmployee = context.Set<Employees>()
.Include (p=>p.Person)
.AsNoTracking()
.FirstOrDefault(e=>e.Id=employee.Id);
//or
var savedEmployee = context.Set<Employees>()
.Include (p=>p.Person)
.AsNoTracking()
.FirstOrDefault(e=>e.PersonId=theSelectedPerson.Id);
but before this you have to fix the bug in the classes
public class Manager
{
public int Id { get; set; }
public int PersonId { get; set; }
public Person Person { get; set; }
}
public class Employee
{
public int Id { get; set; }
public int PersonId { get; set; }
public Person Person { get; set; }
}

Include function returns error saying A specified Include path is not valid

I am trying to get Products data with a relational table data called- Transections. But when I try to use .Include like below I am getting an error. Please give me hints and tell me how can I fix it?
using (var ctx = new ML_DatabaseEntities())
{
var Items = ctx.Products.Include("Transactions").ToList();
}
Error: A specified Include path is not valid. The EntityType
'ML_DatabaseModel.Products' does not declare a navigation property
with the name 'Transactions'.
I assume you have a one-to-many relationship between Product and Transactions. So you need to add navigation property to Product:
public class Product {
public int ProductID { get; set; }
public virtual ICollection<Transaction> Transactions { get; set; }
}
Then your Transaction class should look something like this:
public class Transaction {
public int TransactionID { get; set; }
public int ProductID { get; set; }
public virtual Product Product { get; set; }
}
And the usage would be:
using (var ctx = new ML_DatabaseEntities())
{
var Items = ctx.Products.Include(p => p.Transactions).ToList();
}
For more insights check you can check this link (EF6 not Core)

Entity Framework Code-First Eager Load parent child - children always empty

This is my first code-first project and have never had an issue doing this in db-first, that I can remember. So, I'm either missing something obvious or there's a rule I missed somewhere. I do not want to use lazy loading, and have to think that it's not required to do this, given the many examples using eager loading.
Very simple. Parent record is Listing, child records are Bid entities:
public class Listing {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ListingID { get; set; }
[Required]
[StringLength(140)]
public string Title { get; set; }
//...etc.
public List<Bid> Bids { get; set; }
public Listing() {
Bids = new List<Bid>();
}
}
public class Bid {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int BidID { get; set; }
[Required]
public decimal Amount { get; set; }
//...etc.
public int ListingID { get; set; }
[ForeignKey("ListingID")]
public Listing Listing { get; set; }
public Bid() {
Listing = new Listing();
}
}
I'm using Include to pull the child records:
using (var db = new MyDbContext()) {
var listing = db.Listings
.Where(x => x.Active == true && x.UserID == "abc123")
.Include(x => x.Bids)
.FirstOrDefault();
}
Child collection exists but is always empty (Count = 0) - it's not pulling the records. They're definitely in the tables. I can manually query this and get those bid records, no problem. Basically, I need a listing and all of its bids, similar to this:
select l.*, b.*
from Listing l
inner join Bid b on l.ListingID = b.ListingID
where l.Active = 1
and l.UserID = 'abc123'
What's missing? This article says I'm doing it right:
https://msdn.microsoft.com/en-us/data/jj574232.aspx
Figured it out. I KNEW I had done this type of relationship before. I found an older project where I did the exact same thing. All I had to do was remove the constructor in the Bid class. Apparently it goes awry if you specify both sides of the relationship? I don't know...but now it works and it's pulling the expected records. Frustrating! Seems like they could add some validation to help you from shooting yourself in the foot, so easily. If it's not valid, it'd be nice to know for certain.
Everything the same as the OP, save for this class:
public class Bid {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int BidID { get; set; }
[Required]
public decimal Amount { get; set; }
//...etc.
public int ListingID { get; set; }
[ForeignKey("ListingID")]
public Listing Listing { get; set; }
//public Bid() {
// Listing = new Listing();
//}
}
Try with removing the constructor for both classes.

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.

Retrieve a list of items where an item exists in one of the items lists

Please excuse the slightly confusing title. I have a model (Project) which contains a list of items (Users).
I would like to retrieve all of the projects, where the current user is a member of the user list for that project.
I've tried:
List<Project> _MemberProjects =
_Db.Projects.Where(p =>
p.Users.Contains(_User)
).ToList();
This results in the following error:
Unable to create a constant value of type 'Nimble.Models.UserAccount'. Only primitive types or enumeration types are supported in this context.
User Model:
public class UserAccount
{
public int UserID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
public ICollection<Project> Projects{....}
}
Project Model
public class Project
{
public int ProjectID { get; set; }
public DateTime CreatedDate { get; set; }
public string ProjectName { get; set; }
public string Owner { get; set; }
public ICollection<UserAccount> Users{...}
public ICollection<ProjectGroup> Groups{...}
}
Haven't tried this, but it might work:
List<Project> _MemberProjects =
_Db.Projects.Where(p =>
p.Users.Any(u => u.UserID == _User.UserID )
).ToList();
The problem is that you are mixing together Linq (the WHERE clause) and a non-Linq Collection operation (Contains). Try using pure Linq. #JamesBond's answer might work.
Are you querying a database? Then a JOIN might be another solution, but the exact syntax depends on how you are storing the relationship between the two tables.

Categories