Entity Framework Core does not save related data - c#

In continuation of yesterday's post
Two Entities
public class Realtor
{
public Realtor()
{
Guid = Guid.NewGuid();
Registration = DateTime.Now;
}
public int Id { get; set; }
public Guid Guid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Registration { get; set; }
public int SubdivId { get; set; }
public Subdiv Subdiv { get; set; }
}
public class Subdiv
{
public Subdiv()
{
Created = DateTime.Now;
}
public int Id { get; set; }
public string Name { get; set; }
public DateTime Created { get; set; }
public List<Realtor> Realtors { get; set; }
}
I spend test
I added one Subdiv (TOSTER TM) and received his ID
Next, I add a Realtor and I push Subdiv property found on the ID, the newly created TOSTER TM
Realtor.Subdiv is an object of type Subdiv. OK.
Then I try to select from the base the newly added Realtor.
Realtor.Subdiv = null OMG!!
We get Subdiv object, which is lacking in Realtor above and see his List<Realtor> = null
Please help in solving this problem.

Try this:
Relator rl = Context.Relators.Include(r => r.Subdiv).First(s => s.Id == id);
Now you can access to Subdiv property
For more related date you can call Include Methods more times:
Relator rl = Context.Relators
.Include(r => r.Subdiv)
.Include(r => r.AnotherRel)
.First(s => s.Id == id);
For Entities with multiple levels in depth:
If Subdir is a collection
Relator rl = Context.Relators
.Include(r => r.Subdiv)
.ThenInclude(sub => sub.SecondLevelDepth)
.First(s => s.Id == id);
if Subdir is an Entity
Relator rl = Context.Relators
.Include(r => r.Subdiv.Select(s => s.SecondLevelDepth)
.First(s => s.Id == id);

The problem is not with saving the related data (it should be saved correctly, you could check that inside the database), but loading it.
EF Core currently does not support lazy loading, so in order to get the related data you need to explicitly request it (the so called eager loading):
Realtor rl = context.Realtors.Include(r => r.Subdiv).First(r => r.Id == id);
For more info, see EF Core: Loading Related Data.

Entity Framework core allows to save related entities, you need to define Fluent API settings for both Master and detail table
public class Order
{
public int Id { get; set; }
public int AddressId { get; set; }
public string DeliveryNotes { get; set; }
public int PurchaseOrderNo { get; set; }
public virtual ICollection<OrderItem> Items { get; set; }
}
public class OrderItem
{
public int Id { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal UserPrice { get; set; }
public string Comment { get; set; }
[ForeignKey("OrderId ")]
public int OrderId { get; set; }
public virtual Order Order { get; set; }
}
Inside the DBConext OnModelCreating method , define the relation like, it will make sure when you have order object, its related or child objects i.e Enrolments will also be saved
modelBuilder.Entity<Order>()
.HasMany(c => c.Items)
.WithOne(e => e.Order);
modelBuilder.Entity<OrderItem>().Ignore(x => x.Order);
Now your code should look like this
Order _order = new Order{ AddressId = 1, DeliveryNotes ="some notes", PurchaseOrderNo =1};
_order.Items = new List< OrderItem>();
_ordert.Items.add(new OrderItem{ ProductName =”Laptop”, Quantity =1, UserPrice =1500.00, Comment =”some testing comments”});
repository.order.insert(_order);
repository.save();

Related

EF Core Many to Many Querying to join 3 tables

I have the following Code:
public class ProductTbl
{
public override int Id { get; set; }
public string ProductName { get; set; }
public List<ProductManufacturer> ProductManufacturer { get; set; } //M2M
}
public class Manufacturer_LKP
{
public override int Id { get; set; }
public string ManufacturerName { get; set; }
public List<ProductManufacturer> ProductManufacturer { get; set; } //M2M
}
public class ProductManufacturer
{
public ProductTbl Product { get; set; }
public int ProductID { get; set; }
public Manufacturer_LKP Manufacturer { get; set; }
public int ManufacturerID { get; set; }
}
public class SupplierTbl
{
public int SupplierID { get; set; }
public string SupplierName { get; set; }
}
public class ProductSuppliertbl
{
public int Id { get; set; }
public ProductTbl Product { get; set; }
public int ProductID { get; set; }
public SuppilerTbl Supplier { get; set; }
public int SupplierID { get; set; }
}
*I need to write Linq query to join all 3 tables (Product,Manufacture,ProductManufacturer) to get ProductName and ManufatureName together in one DB trip
*When I do the following I missed the Manufacture object (Manufacture=Null)
DbSet<ProductTbl>()
.Where(a => a.Id == 5)
.AsNoTracking()
.Include(a => a.ProductType)
.Include(a => a.ProductManufacturer)
Above Linq Just joint Product table with ProductManufacture Table So I cannot Get "ManufactureName"
So Is there is any way to join the 3 tables to get ManufactureName beside the ProductName in one DB trip?
Projection is your friend when trying to load related data. The issue with many-to-many is that you are saying a product has many manufacturers, while at the same time it has many suppliers
The Product would need a reference to the ProductSuppliers for that product to easily manage the many suppliers requirement.
var productData = context.Products
.Select(p => new
{
p.ProductName,
ManufacturerNames = p.ProductManufacturers.Select(pm => pm.Manufacturer.ManufacturerName).ToList(),
SupplierNames = x.ProductSuppliers.Select(ps => ps.Supplier.SupplierName).ToList()
}).ToList();
This gives you a list of products, with each product's associated manufacturer names and supplier names. With that data you can format output how you see fit.
If you want the entities themselves, then the missing bit is ThenInclude:
var products = context.Products
.Include(p => p.ProductManufacturers)
.ThenInclude(pm => pm.Manufacturer)
.Include(p => p.ProductSuppliers)
.ThenInclude(ps => ps.Supplier)
.AsNoTracking()
.ToList();
This would load the entire entity graph.
If you don't want or cannot put a ProductSuppliers collection in product then you can build the query entirely from the ProductSupplier, but it's a bit messier.
If you are using EF Core 5 and your joining entities (ProductManufacturer/ProductSupplier) are just simply the FK references to their respective entities, then you can do away with the joining entity and let EF manage it behind the scenes. Product would just contain a collection of Manufacturers and a collection of Suppliers. These can be configured still with a HasMany..WithMany, but makes queries a lot cleaner to look at without the intermediate entities.
I.e.
var productData = context.Products
.Select(p => new
{
p.ProductName,
ManufacturerNames = p.Manufacturers.Select(m => m.ManufacturerName).ToList(),
SupplierNames = x.Suppliers.Select(s => ps.SupplierName).ToList()
}).ToList();
and
var products = context.Products
.Include(p => p.Manufacturers)
.Include(p => p.Suppliers)
.AsNoTracking()
.ToList();
... respectively. Intermediate joining entities are only needed if there are additional properties you want to access in the joining entity. (I.e. CreatedBy/At, etc.)
try this
var list = context.ProductManufactures
.Select(i => new
{
ProductName = i.Product.ProductName,
ManufacturerName = i.Manufacturer.ManufacturerName,
SupplierNames = i.Product.ProductSuppliers.Select(s => s.SupplierName).ToList()
}).ToList();
or you can try this too
var productData = context.Products
.Select(i => new
{
ProductName= i.ProductName,
ManufacturerNames = i.ProductManufacturers.Select(m => m.Manufacturer.ManufacturerName),
SupplierNames = i.ProductSuppliers.Select(s => s.Supplier.SupSupplierName)
}).ToList();
but before this you have to fix some navigation properties
public class Product
{
public int Id { get; set; }
public string ProductName { get; set; }
public List<ProductManufacturer> ProductManufacturers { get; set; }
public List<ProductSupplier> ProductSuppliers { get; set; }
}
public class Supplier
{
public int SupplierID { get; set; }
public string SupplierName { get; set; }
public List<ProductSupplier> ProductSuppliers { get; set; }
}
public class ProductSupplier
{
public int Id { get; set; }
public Product Product { get; set; }
public int ProductID { get; set; }
public Supplier Supplier { get; set; }
public int SupplierID { get; set; }
}
public class Manufacturer_LKP
{
public int Id { get; set; }
public string ManufacturerName { get; set; }
public List<ProductManufacturer> ProductManufacturer { get; set; }
}
public class ProductManufacturer
{
public Product Product { get; set; }
public int ProductID { get; set; }
public Manufacturer_LKP Manufacturer { get; set; }
public int ManufacturerID { get; set; }
}

How to use an ICollection navigation property with include()

I am trying to get each user with its projects using entity framework core in a web api project in the controller with linq
I tried with this query but it gave me an empty object
var users = _context.Users.Include(x => x.userProjects.Select(up => up.UserId == x.Id)).ToListAsync();
I also tried this one and got the same result
var users = _context.Users.Include(x => x.userProjects.Where(up => up.UserId == x.Id)).ToListAsync();
This is the User class
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<UserProject> userProjects { get; set; }
}
This is the Project class
public class Project
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<User> Users { get; set; }
public ICollection<UserProject> UserProjects { get; set; }
}
and this is the UserProject class
public class UserProject
{
[ForeignKey("User")]
public int UserId { get; set; }
public User User { get; set; }
[ForeignKey("Project")]
public int ProjectId { get; set; }
public Project Project { get; set; }
}
I want to get a json with each user and an array of its projects
For multiple level of includes, you need ThenInclude.
var users = _context.Users
.Include(x => x.userProjects)
.ThenInclude(y => y.Project)
.ToListAsync();
var users = _context.Users.Include(u => u.userProjects)
.ThenInclude(up => up.Projects))
.Where(u => u.UserId == Id)
.ToListAsync();
What happens here:
You will retrieve all users which have UserId = yourId and also all the UserProjects and Projects of those users.
Example of code which shows you you can access all the projects of the first returned user:
var projectsForFirstUser = users.First().Select(x => x.UserProjects).Select(x => x.Project).ToList();
EDIT: Modified to ThenInclude because of EF Core.

EF include list is always null

By some reason EF wont load the included list properly so it ends up being null all the time.
Here is the entities i'm using:
[Table("searchprofilepush")]
public class SearchProfilePush
{
public int Id { get; set; }
public int AccountId { get; set; }
public bool Push { get; set; }
public int UserPushId { get; set; }
public UserPush UserPush { get; set; }
public int SearchProfileId { get; set; }
public SearchProfile SearchProfile { get; set; }
public ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
}
[Table("searchprofilemediatypepush")]
public class SearchProfileMediaTypePush
{
public int Id { get; set; }
public MediaTypeType MediaType { get; set; }
public bool Push { get; set; }
public int SearchProfilePushId { get; set; }
public SearchProfilePush SearchProfilePush { get; set; }
}
Then when i'm trying to do this:
var searchProfilePush = _dataContext.SearchProfilePush.Include(w => w.SearchProfileMediaTypePush).FirstOrDefault(w => w.AccountId == accountId && w.SearchProfileId == searchProfileId);
My included list is always null.
I guess it's some obvious reason why this doesn't work but i just can't figure it out.
Thanks!
EDIT:
Here is the sql query:
SELECT \"Extent1\".\"id\", \"Extent1\".\"accountid\", \"Extent1\".\"push\", \"Extent1\".\"userpushid\", \"Extent1\".\"searchprofileid\" FROM \"public\".\"searchprofilepush\" AS \"Extent1\" WHERE \"Extent1\".\"accountid\" = #p__linq__0 AND #p__linq__0 IS NOT NULL AND (\"Extent1\".\"searchprofileid\" = #p__linq__1 AND #p__linq__1 IS NOT NULL) LIMIT 1
EDIT 2:
I have now mapped my entities both way and the list is still always null.
Edit 3:
This is how i created my database tables.
The documentation I read for loading related entities has some differences with the sample code and your code. https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
First, when you define your ICollection, there is no keyword virtual:
public virtual ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
Next, in the example close to yours, where they load related items using a query, the first or default is not using a boolean expression. The selective expression is in a where clause:
// Load one blogs and its related posts
var blog1 = context.Blogs
.Where(b => b.Name == "ADO.NET Blog")
.Include(b => b.Posts)
.FirstOrDefault();
So you can try:
var searchProfilePush = _dataContext.SearchProfilePush
.Where(w => w.AccountId == accountId && w.SearchProfileId == searchProfileId)
.Include(w => w.SearchProfileMediaTypePush)
.FirstOrDefault();
Can you make these two changes and try again?
A few things will be an issue here. You have no keys defined or FKs for the relationship:
[Table("searchprofilepush")]
public class SearchProfilePush
{
[Key]
public int Id { get; set; }
public int AccountId { get; set; }
public bool Push { get; set; }
public int UserPushId { get; set; }
public UserPush UserPush { get; set; }
public int SearchProfileId { get; set; }
public SearchProfile SearchProfile { get; set; }
public ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
}
[Table("searchprofilemediatypepush")]
public class SearchProfileMediaTypePush
{
[Key]
public int Id { get; set; }
public MediaTypeType MediaType { get; set; }
public bool Push { get; set; }
public int SearchProfilePushId { get; set; }
[ForeignKey("SearchProfilePushId")]
public SearchProfilePush SearchProfilePush { get; set; }
}
Personally I prefer to explicitly map out the relationships using EntityTypeConfiguration classes, but alternatively they can be set up in the Context's OnModelCreating. As a starting point have a look at http://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx for basic EF relationship configuration.
for a SearchProfilePush configuration:
modelBuilder.Entity<SearchProfilePush>()
.HasMany(x => x.SearchProfileMediaTypePush)
.WithRequired(x => x.SearchProfilePush)
.HasForeignKey(x => x.SearchProfilePushId);

Entity Framework nested table query to JSON

public class Module
{
public int id { get; set; }
public string moduleName { get; set; }
//navigation property
public virtual HashSet<Policy> policies { get; set; }
}
public class Policy
{
public int id { get; set; }
//foreign keys
public int subscriberId { get; set; }
//navigation properties
public virtual Subscriber subscriber { get; set; }
}
public class Subscriber
{
public int id { get; set; }
public string name { get; set; }
public int subscriptionId { get; set; }
// Navigation property
public virtual HashSet<Policy> policies { get; set; }
}
I have 3 related objects.
Module - Policy - Subscriber
A module has multiple policies
A policy has one subscriber
I need to list all the policies and subscribers under a certain module in JSON format. Due to the posts that I found on web I created this query:
return db.modules
.Where(m => m.id == id)
.Include (m => m.policies.Select(p => p.subscriber))
.Select(m => new {
m.id,
m.moduleName,
m.policies
}) ;
This only gives the result below. As you can see the details of Subscriber entity under policies are not present (NULL) :( What is wrong?
[{"id":1,"moduleName":"module1",
"policies":[{"id":1,"subscriberId":1,"subscriber":null}]}]
Since you are using dynamics in your Select method, you have to build it all out like this:
return db.modules
.Where(m => m.id == id)
.Include (m => m.policies.Select(p => p.subscriber))
.Select(m => new {
m.id,
m.moduleName,
policies = m.policies.Select(p => new
{
p.id,
p.subscriberId,
subscriber = new
{
p.subscriber.id,
p.subscriber.name,
p.subscriber.subscriptionId,
}
}
});
I typically use real Dto classes so if the Dto ever needs to be updated, refactoring will work properly. I would also consider using a DtoFactory to handle the construction, but you could do it with linq like this:
public class ModuleDto
{
public int id { get; set; }
public string moduleName { get; set; }
public IEnumerable<PolicyDto> policies { get; set; }
}
public class PolicyDto
{
public int id { get; set; }
public int subscriberId { get; set; }
public SubscriberDto subscriber { get; set; }
}
public class SubscriberDto
{
public int id { get; set; }
public string name { get; set; }
public int subscriptionId { get; set; }
}
...other code here...
return db.modules
.Where(m => m.id == id)
.Include (m => m.policies.Select(p => p.subscriber))
.Select(m => new ModuleDto {
m.id,
m.moduleName,
policies = m.policies.Select(p => new PolicyDto
{
p.id,
p.subscriberId,
subscriber = new SubsciberDto
{
p.subscriber.id,
p.subscriber.name,
p.subscriber.subscriptionId,
}
}
});
It gets a little messy to read the linq statement. This is why I typically use a DtoFactory to generate the Dtos from the models.

How can I get the data from fields of an .Include in a LINQ Query?

I am using Entity Framework 5 and I have these classes. What I want to do is to be able to get the data to populate the view listed below:
public partial class Subject
{
public int SubjectId { get; set; }
public string Name { get; set; }
public virtual ICollection<Topic> Topics { get; set; }
}
public partial class Topic
{
public int TopicId { get; set; }
public string Name { get; set; }
public int SubjectId { get; set; }
public virtual Subject Subject { get; set; }
public virtual ICollection<SubTopic> SubTopics { get; set; }
}
public partial class SubTopic
{
public int SubTopicId { get; set; }
public string Name { get; set; }
public int TopicId { get; set; }
public virtual Topic Topic { get; set; }
}
Now I am trying to write a LINQ query to populate this class:
public class TopicSubTopicSelect
{
public int TopicId { get; set; }
public int SubTopicId { get; set; }
public string TopicName { get; set; }
public string SubTopicName { get; set; }
}
So far I have this:
return _subjectsRepository
.GetAll()
.Where(s => s.SubjectId == subjectId)
.Include(s => s.Topics.SelectMany(t => t.SubTopics))
.AsEnumerable()
.Select(item => new TopicSubTopicSelect(item.TopicId <<<
item.SubTopicId <<
item.Topic.Name <<
item.Name <<))
.ToList();
Can someone tell me how I can get data from the fields I marked with <<. I tried to do .item.Topic.TopicId etc but that does not seem to work.
You shouldn't start from Subject. You just start from SubTopic Repository, and you won't even need to use .Include. Do it like this:
_subTopicRepository
.GetAll()
.Where(s => s.Topic.SubjectId == subjectId)
.Select(s => new TopicSubTopicSelect()
{
TopicId = s.TopidId,
SubTopicId = s.SubTopicId,
TopicName = s.Topic.Name,
SubTopicName = s.Name
})
.ToList();
As I mentioned in my comment on ataravati's answer, you shouldn't actually have a SubTopicRepository so you are correct in starting at SubjectsRepository however you are querying by the Subject ID so you shouldn't be going via GetAll(), you should have a Get(int id) method. The include should be handled as an implementation detail inside Get as the children (SubTopics) are part of the Subject. That makes the method call look like this instead:
return _subjectsRepository
.Get(subjectId)
.SelectMany(subject => subject.SubTopics))
.Select(subTopic => new TopicSubTopicSelect
{
TopicId = subTopic.TopicId,
SubTopicId = subTopic.SubTopicId,
TopicName = subTopic.Topic.Name,
SubTopicName = subTopic.Name
}).ToList();

Categories

Resources