I want to know whether EF CodeFirst will automatically track "child" objects in the example below.
var db = MyDataContext();
var order = db.Orders.Find(orderId);
order.AddOrderLine("Fancy Product");
db.Commit();
Here are my (simplified) domain entities
public class OrderLine {
public Guid OrderLineId { get; private set; }
public Guid OrderId { get; private set; }
public string Description { get; private set; }
public OrderLine(Guid orderId, string description) {
OrderLineId = Guid.NewGuid();
OrderId = orderId;
Description = description;
}
}
public class Order : Aggregate {
public Guid OrderId { get; private set; }
public ICollection<OrderLine> OrderLines { get; private set; }
public void AddOrderLine(string description) {
OrderLines.Add(new OrderLine(OrderId, description));
}
}
Yes, when you get your Order from context and add the new OrderLine, DbContext will insert it to database calling SaveChanges. It will also track all changes to loaded OrderLines. The only exception can be deleting existing OrderLine. If your OrderLine has PK only OrderLineId removing OrderLine from Order.OrderLines collectin will not delete OrderLine in database but instead it will set its OrderId to null (= exception in your case). If both OrderLineId and OrderId are PK in your OrderLine entity removing OrderLine from Order.OrderLines will also delete OrderLine in database.
Related
I have 3 Entities Product, Supplier & Contract, Product & Supplier each have as reference a Collection of each other which creates a ProductSupplier Table.
To add an extra property on this Many to Many relation I have created a 3rd Entity ProductForSupplier which holds 1 Product, 1 Supplier and a extra string Property ProductNumber.
In addition I have created another Entity ProductSupplierForContract which holds a ProductForSupplier & a Contract. When I seed some data for test I can observe that the ProductSupplierForContract doesn't have the product & Supplier Id value while they are present in my ProductForSupplier Entity but id does have the ProductForSupplierId of the record.
How can I remove these 2 properties in the table ProductSupplierForContract since I have the Id of the table holding these 2 values?
Entities:
public class Product : BaseEntity // BaseEntity just holds an Id and a date
{
public ICollection<ProductSupplierForContract> ProductSupplierForContracts { get; set; }
public ICollection<ProductForSupplier> ProductForSuppliers { get; set; }
}
public class Supplier : BaseEntity
{
public ICollection<ProductForSupplier> ProductForSuppliers { get; set; }
public ICollection<ProductSupplierForContract> ProductSupplierForContracts { get; set; }
}
public class Contract : BaseEntity
{
public ICollection<ProductSupplierForContract> ProductSupplierForContracts { get; set; }
}
public class ProductForSupplier:BaseEntity
{
public string ProductNumber{ get; set; }
[Required]
public Product Product { get; set; }
[Required]
public Supplier Supplier { get; set; }
}
public class ProductSupplierForContract: BaseEntity
{
[Required]
public ProductForSupplier ProductForSupplier { get; set; }
[Required]
public Contract Contract { get; set; }
}
Seeding method
protected override void Seed(TestDbContext context)
{
Supplier supplier1 = new Supplier("Microsoft");
context.Suppliers.Add(supplier1);
Product product1 = new Product("test product 1");
context.Products.Add(product1);
Contract contract = new Contract("Contract 1");
context.Contracts.Add(contract);
ProductForSupplier pfs = new ProductForSupplier("123productNumber");
pfs.Supplier = supplier1;
pfs.Product = product1;
context.ProductForSuppliers.Add(pfs);
ProductSupplierForContract psfc = new ProductSupplierForContract(pfs, contract);
context.ProductSupplierForContracts.Add(psfc);
base.Seed(context);
}
Silly me,
I removed the ProductSupplierForContract reference in both my Supplier & Product Entity and that gave me what i want since that was what created these.
Removed this line in both Entities:
public ICollection<ProductSupplierForContract> ProductSupplierForContracts { get; set; }
New to EF Core...
I have the following EF Core class setup, but I am running into a problem loading the data into a view. When all data is populated for each table everything loads as is, however if for example I have an order and order line that has no orders received only the order loads. The orderline will not load.
Classes:
public class Order {
public Guid OrderID { get; set;}
public ICollection<OrderLine> OrderLines { get; set; }
}
public class OrderLine {
public Guid OrderLineId { get; set; }
public int OrderQty { get; set; }
public Order Order { get; set; }
public Guid OrderID { get; set; }
public ICollection<OrderReceived> OrdersReceived { get; set; }
}
public class OrderReceived {
public Guid OrderReceivedID { get; set; }
public int QuantityReceived { get; set; }
public OrderLine OrderLine { get; set; }
public Guid OrderLineId { get; set; }
}
My controller looks like this:
public async Task<IActionResult> Details(Guid? id) {
if (id == null) {
return NotFound();
}
var orderDetail = await _context.Orders
.Include(ol => ol.OrderLines)
.ThenInclude(rec => rec.OrdersReceived)
.SingleOrDefaultAsync(o => o.OrderId = id);
}
Ex: Order A has 1 order line and 2 OrdersReceived. This loads perfectly.
Ex: Order B has 1 order line and no orders received. Only the Order detail loads everything below that does not load (orderline or ordersreceived). I guess I'm looking for something more like a left join for the ordersreceived. I'm just not that familiar with EF Core.
Hopefully I explained this correcly. Any help is greatly appreciated. I did find this post with a similar question: ef core linq filtered child entities. Is best practice to implement a viewmodel for this type of situation?
Thank you,
E
I am new to MVC and this is my function. There are three tables (Order, OrderNotes, Notes), ID is their primary key. One Order can have many Notes, the table OrderNotes has foreign key OrderID(from Booking table) and NotesID(from Notes table). I want to have a Order Edit page to display individual Order (FirstName, LastName), also display a list of its Notes. Here is my DB structure:
Booking table:
{ID,
FirstName,
LastName
}
BookingNotes table:
{ID,
BookingID,
NotesID
}
Notes table:
{ID,
NoteName,
StatusID
}
So how can I implement the list of Notes since it's from multiple tables? It will be able to Create New Note, Delete existing Note in the list row record, not Edit. Linq used in DB query. Thanks.
It would be a better idea to have only 2 tables:
public class Book
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
// Navigational properties
public virtual List<Note> Notes { get; set; }
}
public class Note
{
public int ID { get; set; }
public int BookID { get; set; }
public string NoteName { get; set; }
public int StatusID { get; set; }
// Navigational properties
public virtual Book Book { get; set; }
public virtual Status Status { get; set; }
}
A third table is useful when you want to reuse the same Note for a different booking. However i think this is not the case.
So to retrieve data for your context make sure you have the DbSet<Book>
public class ApplicationDbContext : DbContext
{
public virtual DbSet<Book> Bookings { get; set; }
}
In your controller (or better in a repository class):
var BookingID = 10; // this is parameter passed to the function
var myBooking = this.dbContext.Bookings
.Include(p => p.Notes)
.ThenInclude(p => p.Status)
.FirstOrDefault(p => p.ID == BookingID);
Map the retrieved booking to a ViewModel, pass it to the View and you're good to go.
I am new to Entity Framework 7 code-first and came across a mapping issue with stored procedure.
Below are two classes where cityid is the relational property:
public class CityList
{
[Key]
public int CityId { get; set; }
[Required]
[MaxLength(50)]
public string CityName { get; set; }
[MaxLength(200)]
public string CityDescription { get; set; }
public ICollection<PointOfInterestList> PointOfInterestList { get; set; } = new List<PointOfInterestList>();
}
public class PointOfInterestList
{
[Key]
public int PointsofInterestId { get; set; }
[Required]
[MaxLength(50)]
public string PointsofInterestName { get; set; }
[MaxLength(200)]
public string PointsofInterestDescription { get; set; }
[ForeignKey("CityId")]
public CityList CityList { get; set; }
public int CityId { get; set; }
}
Using DbContext to populate the CityList object from a stored procedure GetCity:
var returnlist = _context.CitiesList.FromSql("GetCity #p0", CityId).ToList();
Stored procedure code:
CREATE PROCEDURE [GetCity]
(#CityId INT = 0)
AS
BEGIN
IF #CityId = 0
SET #CityId = NULL
SELECT
c.Id CityId, C.Description CityDescription, C.Name CityName--,c.Country
,p.Id PointsofInterestId, p.Name PointsofInterestName, p.Description PointsofInterestDescription
FROM
dbo.Cities c
INNER JOIN
dbo.PointOfInterest p ON p.CityId = c.Id
WHERE
c.Id = ISNULL(#CityId, c.Id)
END
The stored procedure gets executed and populates the CityList collection, but the PointOfInterestList collection within each CityList object does not get mapped with the stored procedure result.
Let me know if I have missed any step.
When you map via a stored procedure, it isn't smart enough to know about any Includes are doing (since you can't do .Include(...)). It only knows the direct fields on the entity you are directly mapping to. Thus, the only way you'd be able to fix this is to create your own DTO that has both the header and lines in it and then have a custom map to push that data into the appropriate entities, along with having to deal with the duplicate header data yourself. (i.e. If you have 2 lines, you'd need 1 header and then add both lines to that header. Uniqueness based on PK would work.)
Edit - Example:
public class MyCustomRowDto
{
public int CityId { get; set; }
public string CityName { get; set; }
public string CityDescription { get; set; }
public int PointsofInterestId { get; set; }
public string PointsofInterestName { get; set; }
public string PointsofInterestDescription { get; set; }
}
Then when you call the stored procedure:
var list = context.Database.SqlQuery<MyCustomRowDto>("...").ToList();
Then you'll need some sort of mapping layer with a dictionary for the primary key. Something like this:
var data = new Dictionary<int, CityList>();
foreach (var item in list) {
CityList entity;
if (!data.TryGet(item.CityId, out entity)) {
entity = new CityList() { CityId = item.CityId, CityName = item.CityName ..., PointOfInterestList = new List<PointOfInterestList>() };
data.Add(item.CityId, entity);
}
entity.PointOfInterest.Add(new PointOfInterestList() { PointOfInterestId = item.PointofInterestId, ..., CityId = item.CityId, CityList = entity
}
return data.Select(a => a.Value).ToList();
I'm trying to insert a new Entity in EF:
public class Ad
{
// Primary properties
public int Kms { get; set; }
// Navigation properties
public virtual Model Model { get; set; }
}
And I receive from the View the model like this (example values):
kms = 222
Model.Id = 3
Then when I do the Add and SaveChanges of the Entity Framework, I get a NULL record inserted in the Model Table (that generated a new ID) and a record in the Ad Table with the new inserted Model Id.
Why is this happening?
Service Layer:
public void CreateAd(CreateAdDto adDto)
{
var adDomain = Mapper.Map<CreateAdDto, Ad>(adDto);
_adRepository.Add(adDomain);
_adRepository.Save();
}
Repository:
public void Add(T entity)
{
_dbSet.Add(entity);
}
public void Save()
{
try
{
_dataContext.SaveChanges();
}
catch (DbEntityValidationException e)
{
var s = e.EntityValidationErrors.ToList();
throw;
}
}
ViewModel:
public class CreateAdViewModel
{
// Primary properties
public string Version { get; set; }
public int Kms { get; set; }
public int Year { get; set; }
public decimal Price { get; set; }
public int Make_Id { get; set; }
public int Model_Id { get; set; }
// Navigation properties
public IEnumerable<SelectListItem> MakeList { get; set; }
public IEnumerable<SelectListItem> ModelList { get; set; }
}
Dto:
public class CreateAdDto
{
// Primary properties
public int Kms { get; set; }
public int Model_Id { get; set; }
}
The Mapping:
Mapper.CreateMap<CreateAdDto, Ad>().ForMember(dest => dest.Model, opt => opt.MapFrom(src => new Model { Id = src.Model_Id }));
If you just call the Add method on the Ad instance, all the related entities are treated as new entities. Therefore you can attach the Model instance first and add the Ad.
context.Models.Attach(ad.Model);
context.Ads.Add(ad);
context.SaveChanges();
SOLUTION BASED ON #Eranga Answer (thanks man!)
Hope this can help somebody else as it help me thanks to #Eranga.
What it was happening is that in the Mapping from DTO to DOMAIN, the Model entity was maping from an Model_Id coming from a Dropdownlist in the View to an Entity Model (as you can see in the mapping line in the question).
Then, when it was added to the database trough Entity Framework, the EF was not aware of the existence of the Model navigation property in the Ad Domain Entity.
So what I had to create to solve this was adding a new method to my repository to handle the possibility to attach the Model Entity to the Ad Entity context:
public void Attach(T entity)
{
_dbSet.Attach(entity);
}
and ad the attach in the Ad Service Create method:
private readonly IRepository<Ad> _adRepository;
private readonly IRepository<Search> _searchRepository;
private readonly IRepository<Model> _modelRepository;
public AdService(IRepository<Ad> adRepository, IRepository<Search> searchRepository, IRepository<Model> modelRepository)
{
_adRepository = adRepository;
_searchRepository = searchRepository;
_modelRepository = modelRepository;
}
public void CreateAd(CreateAdDto adDto)
{
var adDomain = Mapper.Map<CreateAdDto, Ad>(adDto);
_modelRepository.Attach(adDomain.Model);
_adRepository.Add(adDomain);
_adRepository.Save();
}
So now, when the adDomain object arrives to the EF trough the _adRepository.Add, it already knows about the existence of the navigation property Model, and can add the new adDomain ojbect.
Before, what it was happening is that, when the adDomain object was arriving at the EF, the EF was not aware of the Model existence so it was creating an null record.
Hope this help anybody else.
Regards.