I have a DBContext that is initializing through DropCreateDatabaseAlways class.
In the Seed method I have this code:
base.Seed(context);
var c1 = new Company { Name = "Samsung"};
var c2 = new Company { Name = "Microsoft"};
context.Companies.AddRange(new List<Company>{c1,c2 });
var phones = new List<Phone>
{
new Phone("Samsung Galaxy S5", 20000, c1),
new Phone("Nokia S1243", 200000, c1),
new Phone("Nokia 930", 10000, c2),
new Phone("Nokia 890", 8900, c2)
};
context.Phones.AddRange(phones);
context.SaveChanges();
And if iterate through phones now, I see that phone.Company is not null.
But when I do this in any other piece of code, phone.Company IS null.
What do I do wrong?
A simple piece of code with null phone.Company:
using (var db = new MyDataModel())
{
var phonesList = db.Phones.ToList();
foreach (var p in phones)
{
System.Console.WriteLine($"Name: {p.Name}
Company: {p.Company}"); // Company is null.
}
}
I can access Company using Join with Companies on phone.companyId, so Companies table exists in DB.
My DataModel class:
public class MyDataModel : DbContext
{
public MyDataModel()
: base("name=MyDataModel")
{
}
static MyDataModel()
{
Database.SetInitializer(new MyContextInializer());
}
public DbSet<Company> Companies { get; set; }
public DbSet<Phone> Phones { get; set; }
}
My Phone class:
namespace DataContext
{
public class Phone
{
public Phone()
{
}
public Phone(string name, int price, Company company)
{
Name = name;
Price = price;
Company = company;
}
public int Id { get; set; }
public string Name { get; set; }
public int Price { get; set; }
public int CompanyId { get; set; }
public Company Company { get; set; }
}
}
If you want to automagically load the companies when you load a phone, you need to add the virtual keyword before the Company property.
public class Phone {
public Phone() {
}
public Phone(string name, int price, Company company)
{
Name = name;
Price = price;
Company = company;
}
public int Id { get; set; }
public string Name { get; set; }
public int Price { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
}
This tells entity framework to automatically load the linked company whenever you retrieve a phone from the database.
Alternatively you can use eager loading when performing a query on phones.
var phones = MyDataModel.Phones.Include(x => x.Company).ToList();
In addition to the cool answer from Immorality, I'll place here these links:
Virtual properties
MSDN - Loading related data strategies
Related
I am using visual studio 2022 react js web app with .net core template. I've sales table in database which uses productId primary key. in react.js I have select menu in which i want to show the product name instead of product id. how could i achieve this?
in component:
// 1 create useStae
const [sales, setSales] = useState([])
//2 call Api
useEffect(() => {
fetch("api/sale/GetSales")
.then(response => { return response.json() })
.then(responseJson => {
setSales(responseJson)
})
}, [])
and in form i have select menu which shows procutId(foreign keys)
{
sales.map((item) => (
<option>{ item.productId}</option>
))
}
</select>
in controller
[HttpGet]
[Route("GetSales")]
public IActionResult GetSales()
{
NavbaseContext db = new NavbaseContext();
List<Sale> saleList = db.Sales.ToList();
return StatusCode(StatusCodes.Status200OK, saleList);
}
inside sales Model:
using System;
using System.Collections.Generic;
namespace Project1.Models;
public partial class Sale
{
public int Id { get; set; }
public int? ProductId { get; set; }
public int? CustomerId { get; set; }
public int? StoreId { get; set; }
public DateTime? DateSold { get; set; }
public virtual Customer? Customer { get; set; }
public virtual Product? Product { get; set; }
public virtual Store? Store { get; set; }
}
To show product name instead of id, there are some minor modifications you need to do in each part of the code. Try to follow and understand.
Include Product object in your Sale class.
Update your API to include the product name in the response. You can use LINQ to join the Sale and Product tables, and select the product name along with other sale properties.
In your component, display product name instead of product id.
Class
public partial class Sale
{
public int Id { get; set; }
public int? ProductId { get; set; }
public int? CustomerId { get; set; }
public int? StoreId { get; set; }
public DateTime? DateSold { get; set; }
public virtual Customer? Customer { get; set; }
public virtual Product? Product { get; set; }
public virtual Store? Store { get; set; }
}
API
[HttpGet]
[Route("GetSales")]
public IActionResult GetSales()
{
NavbaseContext db = new NavbaseContext();
var saleList = db.Sales
.Include(s => s.Product) // Include the product object to be able to access its properties
.Select(s => new { s.Id, s.CustomerId, s.StoreId, s.DateSold, ProductName = s.Product.Name })
.ToList();
return StatusCode(StatusCodes.Status200OK, saleList);
}
Component
<select>
{sales.map((item) => (
<option key={item.Id} value={item.Id}>{item.ProductName}</option>
))}
</select>
On EF core have Two tables(Page, Group) both have many to many relations with junction table GroupPage. Want to get all pages data with junction table related data based on groupId as like bellow.
If you construct your EF relation correctly you should not have a GroupPage entity.
See Entity Framework Database First many-to-many on how to construct your EF EDM correctly.
Once you have your EDM correctly mapped, you should have the classes
public class Page
{
public int Id { get; set; }
public ICollection<Group> Groups { get; set; }
...
}
public class Group
{
public int Id { get; set; }
public ICollection<Page> Pages { get; set; }
...
}
Then you just need to do the following
public IQueryable<Page> GetPages(int groupId)
{
return from group in _context.Groups
where group.Id == groupId
from page in group.Pages
select page;
}
The following syntax is self-descriptive. Here are the entities structure and Page Dto.
public class Page
{
public int Id { get; set; }
public ICollection<Group> Groups { get; set; }
...
}
public class Group
{
public int Id { get; set; }
public ICollection<Page> Pages { get; set; }
...
}
public class PageGroup
{
public int PageId { get; set; }
public Page Page { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
}
public class PagesDto
{
public string Name { get; set; }
public int GroupId { get; set; }
public int PageId { get; set; }
public string Description { get; set; }
public string Tab { get; set; }
public string Module { get; set; }
public bool? IsActive { get; set; }
public bool? IsDefault { get; set; }
public PagesDto()
{
IsActive = false;
IsDefault = false;
}
}
Following function help us to get group related pages information.
public async Task<List<PagesDto>> GetAllPagesByGroupId(int selectedGroupId)
{
//get all pages
var pages = await _pagesRepository.GetAll().Select(p => new PagesDto {
PageId = p.Id,
Name = p.Name,
GroupId = 0
}).ToListAsync();
//get group ralated pages
var selectedGroupPageIds = _groupPagesRepository
.GetAll()
.Where(p => p.GroupId == selectedGroupId)
.Select(p => p.PageId);
//update page information base on group related pages info.
foreach (var item in pages.Where(p=>selectedGroupPageIds.Contains(p.PageId)))
{
item.GroupId = selectedGroupId;
}
return pages;
}
I am using EF Core and I tried to create a one-to-one relationship between three tables (Car, ElectricCar and PetrolCar)
public class Car
{
public int Id { get; set; }
public string RegistrationNumber { get; set; }
public ElectricCar Company { get; set; }
public PetrolCar Trust { get; set; }
}
public class ElectricCar
{
public int ElectricCarId { get; set; }
public double BatteryCapacityWattage{ get; set; }
public int CarId { get; set; }
public Car Car { get; set; }
}
public class PetrolCar
{
public int PetrolCarId { get; set; }
public double TankCapacity { get; set; }
public int CarId { get; set; }
public Car Car { get; set; }
}
public partial class CarDbContext : Microsoft.EntityFrameworkCore.DbContext
{
public CarDbContext()
{
}
public CarDbContext(DbContextOptions<CarDbContext> options)
: base(options)
{
}
public DbSet<ElectricCar> ElectricCar { get; set; }
public DbSet<Car> Car { get; set; }
public DbSet<PetrolCar> PetrolCar { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("Server=DESKTOP-PC\\SQLLOCAL;Database=OneToOneEFCoreCar;Trusted_Connection=True;");
}
}
}
and the code that inserts the data:
CarDbContext context = new CarDbContext();
context.Car.Add(new Car
{
RegistrationNumber = "EL123",
Company = new ElectricCar() { BatteryCapacityWattage = 2000 }
});
context.Car.Add(new Car
{
RegistrationNumber = "PETR123",
Trust = new PetrolCar() { TankCapacity = 50 }
});
context.SaveChanges();
That works without any issue and creates the following data
When I go to the PetrolCar I insert a new row with CarId = 1 and it accepts it without giving any error although that CarId is used in the ElectricCar table as CarId.
Is there any way to restrict this?
If you're entirely set on keeping your object models / data structure the same as it is above then a unique constraint across the two tables isnt really natively achievable.
One possible in code solution (though its not particularly clean, so I would suggest restructuring your data over this, though that seems to be something you would like to avoid) is to override the SaveChanges method.
something along the lines of:
public override SaveChanges()
{
var petrolCars = ChangeTracker.Entries().Where(e is PetrolCar).ToList();
foreach(var pCar in petrolCars)
{
if(query the database for electric cars to see if car id exists)
{
do some sort of error processing and avoid saving;
}
}
base.SaveChanges();
}
it does mean creating a context class that inherits from the default context, though it adds a lot of flexibility in terms of doing something like this (obviously you would want to handle the other cases too of cars having the same id in the other direction)
In order to create a more elegant solution I'm curios to know your suggestion about a solution to persist a collection.
I've a collection stored on DB.
This collection go to a webpage in a viewmodel.
When the go back from the webpage to the controller I need to persist the modified collection to the same DB.
The simple solution is to delete the stored collection and recreate all rows.
I need a more elegant solution to mix the collections and delete not present record, update similar records ad insert new rows.
this is my Models and ViewModels.
public class CustomerModel
{
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<PreferredAirportModel> PreferedAirports { get; set; }
}
public class AirportModel
{
public virtual string Id { get; set; }
public virtual string AirportName { get; set; }
}
public class PreferredAirportModel
{
public virtual AirportModel Airport { get; set; }
public virtual int CheckInMinutes { get; set; }
}
// ViewModels
public class CustomerViewModel
{
[Required]
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<PreferredAirporViewtModel> PreferedAirports { get; set; }
}
public class PreferredAirporViewtModel
{
[Required]
public virtual string AirportId { get; set; }
[Required]
public virtual int CheckInMinutes { get; set; }
}
And this is the controller with not elegant solution.
public class CustomerController
{
public ActionResult Save(string id, CustomerViewModel viewModel)
{
var session = SessionFactory.CurrentSession;
var customer = session.Query<CustomerModel>().SingleOrDefault(el => el.Id == id);
customer.Name = viewModel.Name;
// How can I Merge collections handling delete, update and inserts ?
var modifiedPreferedAirports = new List<PreferredAirportModel>();
var modifiedPreferedAirportsVm = new List<PreferredAirporViewtModel>();
// Update every common Airport
foreach (var airport in viewModel.PreferedAirports)
{
foreach (var custPa in customer.PreferedAirports)
{
if (custPa.Airport.Id == airport.AirportId)
{
modifiedPreferedAirports.Add(custPa);
modifiedPreferedAirportsVm.Add(airport);
custPa.CheckInMinutes = airport.CheckInMinutes;
}
}
}
// Remove common airports from ViewModel
modifiedPreferedAirportsVm.ForEach(el => viewModel.PreferedAirports.Remove(el));
// Remove deleted airports from model
var toDelete = customer.PreferedAirports.Except(modifiedPreferedAirports);
toDelete.ForEach(el => customer.PreferedAirports.Remove(el));
// Add new Airports
var toAdd = viewModel.PreferedAirports.Select(el => new PreferredAirportModel
{
Airport =
session.Query<AirportModel>().
SingleOrDefault(a => a.Id == el.AirportId),
CheckInMinutes = el.CheckInMinutes
});
toAdd.ForEach(el => customer.PreferedAirports.Add(el));
session.Save(customer);
return View();
}
}
My environment is ASP.NET MVC 4, nHibernate, Automapper, SQL Server.
Well, if "elegant" is just "don't clear and recreate all" (untested) :
var airports = customer.PreferedAirports;
var viewModelAirports = viewModel.PreferredAirports;
foreach (var airport in airports) {
//modify common airports
var viewModelAirport = viewModelAirports.FirstOrDefault(m => m.AirportId == airport.AirportId);
if (viewModelAirport != null) {
airport.X = viewModelAirport.X;
airport.Z = viewModelAirport.Z;
//remove commonAirports from List
viewModelAirports.Remove(viewModelAirport);
continue;
}
//delete airports not present in ViewModel
customer.PreferedAirports.Remove(airport);
}
//add new airports
foreach (var viewModelAirport in viewModelAirports) {
customer.PreferedAirports.Add(new PreferredAirportModel {
Airport = session.Query<AirportModel>().SingleOrDefault(a => a.Id == el.AirportId),
CheckInMinutes = el.CheckInMinutes
});
}
session.Save(customer);
in my Winform Application, I have Suppliers, Customers, Transport Companies. They are similar as they are basically some kind of Contacts, however they do different slightly in term of available fields.
For example Suppliers need have StartDate and EndDate fields. And currently even though Suppliers and Customers could have more than one contact person\entity, but we are not going do that in these release, but the Transport companies will have more than one contact person\entity and addresses. At the same time, the Supplier and Customer do require PO Address and Delivery Address, and two phone numbers just in case.
Currently in my Code First Entities, I have Suppliers, Customers and Transport Companies each contains a PrimaryContact which is a Contact Type, and for each Contact type, I have a ICollection of Address and Phone which in turn store one or more than one address and phone information. The difference is that Transport Companies will have a collection of Contact in addition of PrimaryContact.
As my understanding, even I have the freedom of design the DB/Entity by myself, there is not always the case that Objects in BLL is exactly mapping of the DB structure underneath.
So the idea is in my BLL layer, I will translate the data from Supplier to BOSupplier to Presentation Layer, and will doing translation to Supplier when get data back from Presentation Layer to DAL. Because in my Presentation Layer, the Supplier will looks like:
public class BOSupplier
{
// Primery key
public int ID { get; set; }
public string Name { get; set; }
public string TaxNumber { get; set; }
public bool InActive { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public string BankAccountNumber { get; set; }
public string BankAccountName { get; set; }
// Property related to Contact Table
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string SkypeName { get; set; }
// Proterty related to Address Table
// PO address Info
public string POAddressLine { get; set; }
public string POCity { get; set; }
public string PORegion { get; set; }
public string POCountry { get; set; }
public string POPostCode { get; set; }
// Delivery AddressLine
public string DelAddressLine { get; set; }
public string DelCity { get; set; }
public string DelRegion { get; set; }
public string DelCountry { get; set; }
public string DelPostCode { get; set; }
// Proterties related to Phone table
public string PhoneNumber1 { get; set; }
public string PhoneNumber2 { get; set; }
}
}
But in my DAL Layer, my Supplier will looks like this:
public class Supplier
{
// Primery key
public int ID { get; set; }
public string Name { get; set; }
public virtual Contact PrimaryContact { get; set; }
public string TaxNumber { get; set; }
public bool InActive { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public string BankAccountNumber { get; set; }
public string BankAccountName { get; set; }
}
Then when I am actually writing code for BLL classes to manage my intermediate BOSupplier object and List which didn't actually mapping back an Entity to DB side. It seems a lots low level code just to transfer/map fields from two slightly different BOSupplier and Supplier, like this:
public static IEnumerable<BOSupplier> GetBOSuppliers()
{
var suppliers = dbContext.Suppliers;
BOSupplier currentSupplier;
foreach (Supplier supplier in suppliers)
{
currentSupplier = new BOSupplier()
{
ID = supplier.ID,
Name = supplier.Name,
Code = supplier.Code,
FirstName = supplier.PrimaryContact.FirstName,
TaxNumber = supplier.TaxNumber
};
// PO Address
Address poAddress = supplier.PrimaryContact.Addresses
.FirstOrDefault<Address>(a => a.AddressTypeValue == (int)AddressTypes.Postal);
if (poAddress != null)
{
currentSupplier.POAddressLine = poAddress.AddressLine1;
currentSupplier.POCity = poAddress.City;
currentSupplier.POCountry = poAddress.Country;
}
// Delivery Address
Address delAddress = supplier.PrimaryContact.Addresses
.FirstOrDefault<Address>(a => a.AddressTypeValue == (int)AddressTypes.Delivery);
if (delAddress != null)
{
currentSupplier.DelAddressLine = delAddress.AddressLine1;
currentSupplier.DelCity = delAddress.City;
currentSupplier.DelCountry = delAddress.Country;
}
// ToDo:
// There is probably more to think about how we want map multi phone numbers into limited two phone numbers
if (supplier.PrimaryContact.Phones.Count > 0)
{
foreach (Phone phone in supplier.PrimaryContact.Phones)
{
if (phone.PhoneType == PhoneTypes.Default)
{
currentSupplier.PhoneNumber1 = phone.PhoneNumber;
}
else
{
currentSupplier.PhoneNumber2 = phone.PhoneNumber;
}
}
}
this.boSupplierList.Add(currentSupplier);
}
return boSupplierList;
}
I am keep thinking: "Maybe my Entity Model should be simpler, or there is some better way of doing what I am trying to?". So please, from your experience, tell me that my Entity model are on over-complex side, or I just need some better way of mapping from BOSuppier to Supplier or some other thoughts.
Your entity model is not complex according to your description of the domain. You can use AutoMapper to map your Supplier to BOSupplier. Here is an example of flattening object graph using AutoMapper.
I see a problem in your GetBOSuppliers(). It uses lazy loading when you access PrimaryContact and Addresses. To avoid the multiple round trips to database you can eager load them as follows.
var suppliers = dbContext.Suppliers.Include(s => s.PrimaryContact.Addresses);