One API call to retrieve all items in the model - entity-framework-core

I created a simple web api using Net Core 2.2. I have this api controller below, that gets one particular dungeon.
It is returning a dungeon as JSON, but it's not returning the MonsterList associated with the dungeon.
So this is my controller:
// GET: api/DungeonLists/5
[HttpGet("{id}")]
public async Task<ActionResult<DungeonList>> GetDungeonList(Guid id)
{
var dungeonList = await _context.DungeonList.FindAsync(id);
if (dungeonList == null)
{
return NotFound();
}
return dungeonList;
}
And here is my model for the Dungeon. As you can see, it has a MonsterList.
public partial class DungeonList
{
public DungeonList()
{
MonsterList = new HashSet<MonsterList>();
}
public Guid DungeonId { get; set; }
public string DungeonName { get; set; }
public string DungeonDesc { get; set; }
public string MapArea { get; set; }
public bool ShowProgress { get; set; }
public bool? DungeonResumable { get; set; }
public virtual ICollection<MonsterList> MonsterList { get; set; }
}
Here is my MonsterList model:
public partial class MonsterList
{
public string MonsterId { get; set; }
public Guid DungeonId { get; set; }
public string MonsterName { get; set; }
public byte? MonsterType { get; set; }
public bool IsBossMonster { get; set; }
public virtual DungeonList Dungeon { get; set; }
}
I want the JSON to also show the list of monsters associated with the dungeon.
Is there a way to do this? Or would I need to make a separate API call?
Thanks!

You need to change your code to the following:
[HttpGet("{id}")]
public async Task<ActionResult<DungeonList>> GetDungeonList(Guid id)
{
var dungeonList = await _context.DungeonList
.Include(i => i.MonsterList)
.FirstOrDefaultAsync(p => p.Id = id);
if (dungeonList == null)
{
return NotFound();
}
return dungeonList;
}
Additionally, since you arent using LazyLoading, you dont need the [virtual] on the MonsterList collection

Related

Failing to pass a complex object from one page to another in .Net Maui

I am trying to pass a complex object from MainPage to a ProductsPage, the object is a model with 4 class lists. Of the 4 class lists only 2 are passing data to the ProductsPage, the other 2 are not. I dont know where i am going wrong, i am using MVVM
My MainPageViewModel is as below
public partial class MainPageViewModel : BaseViewModel
{
public ObservableCollection<LogInModel> LogInModels { get; } = new();
public MainPageViewModel()
{
}
[ObservableProperty]
LogInModel logInModel;
[RelayCommand]
async Task GoToRetailAsync()
{
if (LogInModels.Count != 0)
LogInModels.Clear();
LogInModels.Add(logInModel);
await Shell.Current.GoToAsync($"{nameof(ProductsPage)}", true,
new Dictionary<string, object>
{
{"shiptoo",LogInModels[0].cat },
{"group",LogInModels[0].grp },
{"products",LogInModels[0].prod },
{"shipto",LogInModels[0].shp }
});
}
}
}
the failing class lists are shiptoo and group
Below is my ProductsViewModel
namespace Tenga.ViewModel
{
[QueryProperty("Products", "products")]
[QueryProperty("Group","group")]
[QueryProperty("Shiptoo","shiptoo")]
[QueryProperty("Shipto", "shipto")]
public partial class ProductsViewModel : BaseViewModel
{
public ProductsViewModel()
{
}
[ObservableProperty]
List<Shiptoo> shppp;
[ObservableProperty]
List<Group> groups;
[ObservableProperty]
List<Products> products;
[ObservableProperty]
List<Shipto> shipto;
}
}
Below is my LogInModel
namespace Tenga.Model
{
public class LogInModel
{
public string OTP { get; set; }
public string CustomerNumber { get; set; }
public string CustomerName { get; set; }
public string Balance { get; set; }
public string OpenToBuy { get; set; }
public string CreditLimit { get; set; }
public string LoginStatus { get; set; }
public string Error { get; set; }
public List<Shipto> shp = new List<Shipto>();
public List<Shiptoo> cat = new List<Shiptoo>();
public List<Group> grp = new List<Group>();
public List<Products> prod = new List<Products>();
}
public class Shipto
{
public string ShipCode { get; set; }
public string ShipDescription { get; set; }
}
public class Products
{
public string ItemCode { get; set; }
public string ItemDescription { get; set; }
public string UOM { get; set; }
public string ConversionFactor { get; set; }
public string Category { get; set; }
public string Group { get; set; }
public byte[] Image { get; set; }
}
public class Shiptoo
{
public string ShipCode { get; set; }
public byte[] Image { get; set; }
}
public class Group
{
public string Category { get; set; }
public string Code { get; set; }
public byte[] Image { get; set; }
}
}
I have tried to review the class all seems alright, i have also tried changing the bindings and result is the same, can some one please help before go crazy
Implement IQueryAttributable in your ViewModel.
And use:
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
Model = query[nameof(MyModel )] as MyModel ;
}
Forget about those annotations. This is better. You cant mistake names, you can run code after/before they are set. I migrated all my code to use this.
Edit: While we are on the subject:
Instead of:
{"shiptoo",LogInModels[0].cat },
You should be using some constants. The name of the model usually. (Something like naming conventions when passing Extras in android, but much more simple).

Error adding an object to a field list Entity Framework

Sorry if I don't respect the conventions, I'm new here.
I have a problem in my learning of ASP.NET Core and Entity Framework.
I have bets and users that can have multiple bets. The bets can have no users (they are just displayed on the site and users add them to their account).
Here are my two models :
Bets :
public class Bets
{
public int Id { get; set; }
public string name { get; set; }
public string description { get; set; }
public double team1 { get; set; }
public double team2 { get; set; }
public string sport { get; set; }
public bool enabled { get; set; } // 1 = yes 2 = no
public virtual User user {get;set;}
public int? UserId {get;set;}
}
User :
public class User
{
public int UserId { get; set; }
public string Username { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
public double moneyBalance { get; set; }
public int admin { get; set; }
public virtual ICollection<Bets> bets {get; set;}
}
Then I tried to add a bet that already existed to a user:
public async Task<bool> addUserBet(int id, Bets bet)
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.UserId == id);
if (!(user.bets == bet))
{
user.bets.Add(bet);
await _context.SaveChangesAsync();
return true;
}
return false;
}
The controller :
[AllowAnonymous]
[HttpPut("addBetUser/{id}")]
public async Task<IActionResult> AddBetUser(int id, BetForAddToUserDto betForAddTo)
{
Bets bets = new Bets();
_mapper.Map(betForAddTo, bets);
await _repo.addUserBet(id, bets);
await _repo.SaveAll();
return StatusCode(200);
}
But I get this error in Postman when I try the endpoint above:
What am I doing wrong that I don't understand? Please help me, thank you :)
I tried to add a bet that already existed to a user
If you have existed a bet and a user.And you want to add this bet to the user.
Here is a working demo like below:
1.Model:
public class Bets
{
public int Id { get; set; }
public string name { get; set; }
public string description { get; set; }
public double team1 { get; set; }
public double team2 { get; set; }
public string sport { get; set; }
public bool enabled { get; set; } // 1 = yes 2 = no
public virtual User user { get; set; }
public int? UserId { get; set; }
}
public class User
{
public int UserId { get; set; }
public string Username { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
public double moneyBalance { get; set; }
public int admin { get; set; }
public virtual IList<Bets> bets { get; set; }
}
public class BetForAddToUserDto
{
public int Id { get; set; }
public string name { get; set; }
public string description { get; set; }
public double team1 { get; set; }
public double team2 { get; set; }
public string sport { get; set; }
public bool enabled { get; set; } // 1 = yes 2 = no
public virtual User user { get; set; }
public int? UserId { get; set; }
}
2.Controller:
public async Task<bool> addUserBet(int id, Bets bet)
{
var user = await _context.User.FirstOrDefaultAsync(u => u.UserId == id);
if (!(user.bets == bet))
{
bet.UserId = id;
_context.Bets.Update(bet);
await _context.SaveChangesAsync();
return true;
}
return false;
}
[AllowAnonymous]
[HttpPut("addBetUser/{id}")]
public async Task<IActionResult> PutUser(int id, BetForAddToUserDto betForAddTo)
{
Bets bets = new Bets();
_mapper.Map(betForAddTo, bets);
var bet = _context.Bets.Find(bets.Id);
await addUserBet(id, bet);
return StatusCode(200);
}
3.Profile:
public class AutoMapping: Profile
{
public AutoMapping()
{
CreateMap<Bets, BetForAddToUserDto>().ReverseMap();
}
}
4.Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(typeof(AutoMapping));
}
This:
if (!(user.bets == bet))
User.bets is a collection, you cannot compare it to a single bet
The right way to check it would be:
if (!(user.bets.Any(b => b.ID == bet.ID)))
Also you are not passing in anything for this parameter:
BetForAddToUserDto betForAddTo

Json response does not contain all the navigation properties EntityFramework Core and ASP .NETCore Web API

I have migrated from Entity Framework 6 to EF Core and also Web Api .net framework to .net core.
I have many to many relationship that I have set up as follows
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var instrumentsToPlaces = modelBuilder.Entity<InstrumentPlace>();
instrumentsToPlaces.ToTable("InstrumentsToPlaces");
instrumentsToPlaces.HasKey(x => new { x.PlaceId, x.InstrumentId });
instrumentsToPlaces.HasOne(i => i.Instrument)
.WithMany(p => p.InstrumentsPlaces)
.HasForeignKey(ip => ip.InstrumentId);
instrumentsToPlaces.HasOne(p => p.Place)
.WithMany(i => i.InstrumentsPlaces)
.HasForeignKey(ip => ip.PlaceId);
var instrumentsToStyle = modelBuilder.Entity<InstrumentStyle>();
instrumentsToStyle.ToTable("InstrumentsToStyles");
instrumentsToStyle.HasKey(x => new { x.StyleId, x.InstrumentId });
instrumentsToStyle.HasOne(i => i.Instrument)
.WithMany(s => s.InstrumentStyles)
.HasForeignKey(si => si.InstrumentId);
instrumentsToStyle.HasOne(s => s.Style)
.WithMany(i => i.InstrumentStyles)
.HasForeignKey(si => si.StyleId);
}
I have included the navigation properties in the repository method as follows
public Instrument GetInstrumentByName(string name)
{
using (var starsAndCatzDbContext = new StarsAndCatzDbContext())
{
var instrument = _starsAndCatzDbContext.Instruments
.Include(a=>a.InstrumentsPlaces)
.ThenInclude(a=>a.Place)
.Include(a=>a.InstrumentStyles)
.ThenInclude(a=>a.Style)
.FirstOrDefault(i => i.Name == name);
return instrument;
}
}
Here are the classes
public class Instrument {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<InstrumentPlace> InstrumentsPlaces { get; set; }
public virtual ICollection<InstrumentStyle> InstrumentStyles { get; set; }
}
public class InstrumentPlace
{
public int InstrumentId { get; set; }
public Instrument Instrument { get; set; }
public int PlaceId { get; set; }
public Place Place { get; set; }
}
public class InstrumentStyle
{
public int InstrumentId { get; set; }
public Instrument Instrument { get; set; }
public int StyleId { get; set; }
public Style Style { get; set; }
}
public class Style {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<InstrumentStyle> InstrumentStyles { get; set; }
}
public class Place {
public int Id { get; set; }
public string Name { get; set; }
public string Division { get; set; }
public int Tier { get; set; }
public string State { get; set; }
public string Postcode { get; set; }
public float? Latitude { get; set; }
public float? Longitude { get; set; }
public virtual ICollection<InstrumentPlace> InstrumentsPlaces { get; set; }
}
The WebAPI method to be called is
[HttpGet("GetInstrumentByName/{suburb}/{instrument}"), Produces("application/json")]
public Instrument GetInstrumentByName(string suburb, string instrument)
{
try
{
var result = _instrumentRepository.GetInstrumentByName(instrument);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new Instrument();
}
}
When I send the request to "/api/instruments/west-end/guitar" I get the expected result when I place a breakpoint before sending the response as follows
As you notice, the Navigation properties are loaded (when I expand the collections I can see all the properties being loaded as well).
However the json response I receive is the following
Any suggestions or am I missing something here?
Thank you all in advanced
Thanks #H. Herzl for giving me a hint.
The solution was found in this other question
services.AddMvc().AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
https://stackoverflow.com/a/40501464/1513346

Returning Entity with its children

Hi I am trying to return all vehicles with their recorded mileage through an api using ASP.Net Core with the following code:
// GET: api/values
[HttpGet]
public IEnumerable<Vehicle> Get()
{
return _context.Vehicles.Include(m=>m.Mileages).ToList();
}
However this only returns the first vehicle with its mileages and not the others (there are five dummy vehicles in the db all with an initial mileage).
If I change the code to:
// GET: api/values
[HttpGet]
public IEnumerable<Vehicle> Get()
{
return _context.Vehicles.ToList();
}
it returns the full list of vehicles but no mileage.
My class files are:
public class Vehicle
{
public Vehicle()
{
Mileages = new List<Mileage>();
}
public int Id { get; set; }
public string Registration { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public Marked Marked { get; set; }
public ICollection<Mileage> Mileages { get; set; }
}
and
public class Mileage
{
public int Id { get; set; }
public DateTime MileageDate { get; set; }
public string RecordedMileage { get; set; }
//Navigation Properties
public int VehicleId { get; set; }
public Vehicle Vehicle { get; set; }
}
thanks for looking!
Tuppers
you can have them auto-load (lazy loading) using proxies... but for that, your foreign entities and collections must be marked virtual in your POCOs:
public class Mileage
{
public int Id { get; set; }
public DateTime MileageDate { get; set; }
public string RecordedMileage { get; set; }
//Navigation Properties
public int VehicleId { get; set; }
public virtual Vehicle Vehicle { get; set; }
}
public class Vehicle
{
public Vehicle()
{
Mileages = new List<Mileage>();
}
public int Id { get; set; }
public string Registration { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public Marked Marked { get; set; }
public virtual ICollection<Mileage> Mileages { get; set; }
}
The proxy creation and lazy loading turned on, but that's the default in EF6.
https://msdn.microsoft.com/en-us/data/jj574232.aspx
Let me know if this works.
Well after a lot of searching I managed to find a solution. I used the following:
[HttpGet]
public IEnumerable<VehicleDto> Get()
{
var query = _context.Vehicles.Select(v => new VehicleDto
{
Registration = v.Registration,
Make = v.Make,
Model = v.Model,
Marked = v.Marked,
Mileages = v.Mileages.Select(m => new MileageDto
{
MileageDate = m.MileageDate,
RecordedMileage = m.RecordedMileage
})
.ToList(),
})
.ToList();
return (IEnumerable<VehicleDto>) query.AsEnumerable();
this doesn't seem to be the most elegant way of doing this, if anyone could offer any advice but it does return what is required.
The DTO's look like:
public class VehicleDto
{
public string Registration { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public Marked Marked { get; set; }
public ICollection<MileageDto> Mileages { get; set; }
}
and
public class MileageDto
{
public DateTime MileageDate { get; set; }
public string RecordedMileage { get; set; }
}
Thanks for taking the time to look at this
Tuppers

BreezeJs: Update to new metadata from changed BreezeController

I've changed and added some properties to my server-side classes but can't get the updated data in my breeze/angular app. The added fields stay blank instead of showing the value. I also can't create an entity that i've added. (error). How can i update the metadata in my breeze/angular app to use the latest version? I've tried to fetch the metadata, but getting the message that it already was fetched.
Breeze: Unable to locate a 'Type' by the name: 'New Class'. Be sure to execute a query or call fetchMetadata first
Update (More Info)
I've created a child class related to my Product class. It's called ProductStockItem, so a Product has many ProductStockItems.
ProductStockItem: (new Class)
public class ProductStockItem
{
public int Id { get; set; }
public int ProductId { get; set; }
public string Size { get; set; }
public int Quantity { get; set; }
public bool UseStockQuantity { get; set; }
public decimal PriceAdjustment { get; set; }
public DateTime? DateAvailable { get; set; }
public int DisplayOrder { get; set; }
public bool Deleted { get; set; }
public State State { get; set; }
public DateTime? DateChanged { get; set; }
public DateTime? DateCreated { get; set; }
public virtual Product Product { get; set; }
}
Product:
public class Product
{
private ICollection<ProductCategory> _productCategories;
private ICollection<ProductManufacturer> _productManufacturers;
private ICollection<ProductPicture> _productPictures;
private ICollection<ProductSpecificationAttribute> _productSpecificationAttributes;
private ICollection<ProductStockItem> _productStockItems;
public int Id { get; set; }
public ProductType ProductType { get; set; }
public int ParentGroupedProductId { get; set; }
public int ManufacturerSizeId { get; set; }
public bool VisibleIndividually { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string MetaTitle { get; set; }
public string MetaDescription { get; set; }
public int DisplayOrder { get; set; }
public bool LimitedToStores { get; set; }
public string Sku { get; set; }
public string UniqueCode { get; set; }
public decimal Price { get; set; }
public decimal OldPrice { get; set; }
public decimal? SpecialPrice { get; set; }
public DateTime? SpecialPriceStartDateTimeUtc { get; set; }
public DateTime? SpecialPriceEndDateTimeUtc { get; set; }
public decimal DiscountPercentage { get; set; }
public bool HasTierPrices { get; set; }
public bool HasStock { get; set; }
public TaxRate TaxRate { get; set; }
public bool SyncToShop { get; set; }
public bool Deleted { get; set; }
public bool Locked { get; set; }
public State State { get; set; }
public DateTime? DateChanged { get; set; }
public DateTime? DateCreated { get; set; }
public virtual ICollection<ProductCategory> ProductCategories
{
get { return _productCategories ?? (_productCategories = new List<ProductCategory>()); }
protected set { _productCategories = value; }
}
public virtual ICollection<ProductManufacturer> ProductManufacturers
{
get { return _productManufacturers ?? (_productManufacturers = new List<ProductManufacturer>()); }
protected set { _productManufacturers = value; }
}
public virtual ICollection<ProductPicture> ProductPictures
{
get { return _productPictures ?? (_productPictures = new List<ProductPicture>()); }
protected set { _productPictures = value; }
}
public virtual ICollection<ProductSpecificationAttribute> ProductSpecificationAttributes
{
get { return _productSpecificationAttributes ?? (_productSpecificationAttributes = new List<ProductSpecificationAttribute>()); }
protected set { _productSpecificationAttributes = value; }
}
public virtual ICollection<ProductStockItem> ProductStockItems
{
get { return _productStockItems ?? (_productStockItems = new List<ProductStockItem>()); }
protected set { _productStockItems = value; }
}
}
Product request:
http://testdomain.local/breeze/DataContext/Products?$filter=Id%20eq%201029&$orderby=Id&$expand=ProductStockItems&
[{"$id":"1","$type":"Erp.Models.ErpModel.Catalog.Product, Erp.Models.ErpModel","Id":1029,"ProductType":"SimpleProduct","ParentGroupedProductId":0,"ManufacturerSizeId":2767,"VisibleIndividually":false,"Name":"Jako Ballenzak Kids - Ash / Action Green","ExtraName":null,"Description":"• Aangenaam functioneel materiaal\nmet moderne oppervlaktestructuur\nvoor de hoogste normen\n• Zeer goede klimaateigenschappen\ndoor actief ademend Twill-Polyester\n• Rekbaar, vormvast en sneldrogend\n\nPolyester-Twill\n100% Polyester,\nbinnenvoering: 100% Polyester","MetaTitle":null,"MetaDescription":null,"DisplayOrder":1,"LimitedToStores":false,"Sku":"9894","UniqueCode":"6_9","Price":34.96,"OldPrice":49.95,"SpecialPrice":null,"SpecialPriceStartDateTime":null,"SpecialPriceEndDateTime":null,"DiscountPercentage":0.00,"HasTierPrices":true,"HasStock":false,"TaxRate":"Tax_21","SyncToShop":true,"Deleted":false,"Locked":false,"State":"Changed","DateChanged":"2014-02-28T10:35:47.733","DateCreated":"2014-02-28T10:35:47.733","ProductCategories":[],"ProductManufacturers":[],"ProductPictures":[],"ProductSpecificationAttributes":[],"ProductStockItems":[]}]
Metadata request:
http://testdomain.local/breeze/DataContext/Metadata
Error Client Side: (create new productStockItem)
Unable to locate a 'Type' by the name: 'ProductStockItem'. Be sure to execute a query or call fetchMetadata first.
function createProductStockItem(initialValues) {
return this.manager.createEntity("ProductStockItem", initialValues);
}
When you rebuild your application, the metadata will get updated. No extra job needed for making the
metadata to get fetched with it's updated state.
Whenever you issue a query on an entity that is included inside the metadata, the updated metadata
will get fetched.
For creating an entity, if you navigate directly to the page of creating an entity prior to any page that includes a query, the metadata won't get fetched in this case.
When you called fetchMetadata(), you still get the error:
Unable to locate a 'Type' by the name: 'New Class'. Be sure to execute a query or call `fetchMetadata` first
That message doesn't indicate that the metadata already was fetched. It still tells you that the entity is unknown and the metadata still not fetched.
Why? Because createEntity() was called before fetchMetadata(). (you can set a break-point and see that in action)
I ran into this before, and what I did, I simply put the fetchMetadata() on application launch.
That will guarantee that it will get fetched first before any call to creating an entity.
Or you can just use a promise:
manager.fetchMetadata().then(createProductStockItem("Initial values"));