EF Core stop recursion on theninclude - c#

I am using .NET Core 3 with the latest Entity Framework Core. I currently have 3 tables, two normal tables and one linking table. They are structured below. I am trying to get the accessory by itself for the vehicle it is linked to, what currently is happening is that it recurses back into itself and loads the vehicle object again (since it has a reference to the linking table). Does anyone know how I can stop this recursion from taking place? I don't want the VehicleAccessory to have the vehicleAccessories array within it, that is causing the recursion.
Call
var result = _context.Vehicles
.Include(x => x.VehicleAccessories)
.ThenInclude(x => x.Accessory)
.Include(x => x.VehicleImages)
.ThenInclude(x => x.Image)
.ToList();
return result;
Call result
{
"id": 1,
"registrationNumber": "123456",
"make": "BMW",
"model": "M3",
"year": 2009,
"kilometers": 10030,
"vehicleIdentificationNumber": "1234567890",
"retailPrice": 100000.00,
"costPrice": 50000.00,
"vehicleAccessories": [
{
"vehicleId": 1,
"accessoryId": 1,
"accessory": {
"id": 1,
"description": "NEW ONE",
"vehicleAccessories": []
}
},
{
"vehicleId": 1,
"accessoryId": 2,
"accessory": {
"id": 2,
"description": "NEW ONE",
"vehicleAccessories": [
{
"vehicleId": 2,
"vehicle": {
"id": 2,
"registrationNumber": "123456",
"make": "BMW",
"model": "M3",
"year": 2009,
"kilometers": 10030,
"vehicleIdentificationNumber": "1234567890",
"retailPrice": 100000.00,
"costPrice": 50000.00,
"vehicleAccessories": [],
"vehicleImages": [],
"dtCreated": "2020-02-03T11:21:48.2245986",
"dtUpdated": "2020-02-03T11:21:48.2245554"
},
"accessoryId": 2
}
]
}
}
],
"vehicleImages": [],
"dtCreated": "2020-02-03T11:20:13.8890848",
"dtUpdated": "2020-02-03T11:20:13.8890488"
}
Vehicle table:
public class Vehicle
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public string RegistrationNumber { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public int Year { get; set; }
public int Kilometers { get; set; }
public string VehicleIdentificationNumber { get; set; }
public decimal RetailPrice { get; set; }
public decimal CostPrice { get; set; }
public ICollection<VehicleAccessory> VehicleAccessories { get; } = new List<VehicleAccessory>();
public ICollection<VehicleImage> VehicleImages { get; set; }
public DateTime DTCreated { get; set; }
public DateTime DTUpdated { get; set; }
}
Accessory Table
public class Accessory
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public string Description { get; set; }
public ICollection<VehicleAccessory> VehicleAccessories { get; } = new List<VehicleAccessory>();
}
Linking Table
public class VehicleAccessory
{
public long VehicleId { get; set; }
public Vehicle Vehicle { get; set; }
public long AccessoryId { get; set; }
public Accessory Accessory { get; set; }
}
Relationship in context
builder.Entity<VehicleAccessory>()
.HasKey(t => new { t.AccessoryId, t.VehicleId });

Related

Is possible return data from two tables in ICollection using LINQ?

This is my first question, I hope to do it correctly.
I am starting to use an ASP.NET Core Web API and I have a question.
I have the following models:
public class Pokemon
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime BirthDate { get; set; }
public ICollection<Review> Reviews { get; set; }
public ICollection<PokemonOwner> PokemonOwners { get; set; }
public ICollection<PokemonCategory> PokemonCategories { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<PokemonCategory> PokemonCategories { get; set; }
}
public class PokemonCategory
{
public int PokemonId { get; set; }
public int CategoryId { get; set; }
public Pokemon Pokemon { get; set; }
public Category Category { get; set; }
}
I'm trying to display in an endpoint of my controller where it shows me together the pokemons and their corresponding category.
I have tried to make a join between the two tables but it is impossible to get the expected result (return the pokemons and its category).
public List<Category> GetPokemonAndCategory(int pokemonid, int categoryid)
{
return _context.Categories
.Include(a => a.PokemonCategories)
.Where(c => c.Id == categoryid).ToList();
}
With this code, I get this data returned:
[
{
"id": 2,
"name": "Water",
"pokemonCategories": [
{
"pokemonId": 2,
"categoryId": 2,
"pokemon": null,
"category": null
}
]
}
]
Can you help me? Thanks!
Return pokemons and categories in the same query
EDIT
This works with cemahseri answer, if i change the DTO for something like this;
public class PokemonCategoryDto
{
public Pokemon Pokemon { get; set; }
// public Category Category { get; set; }
}
but i get this result;
{
"pokemon": {
"id": 2,
"name": "Squirtle",
"birthDate": "1903-01-01T00:00:00",
"reviews": null,
"pokemonOwners": null,
"pokemonCategories": [
{
"pokemonId": 2,
"categoryId": 2,
"pokemon": null,
"category": {
"id": 2,
"name": "Water",
"pokemonCategories": [
null
]
}
}
]
}
}
i think is because mi pokemon class have the other classes, how i can not show it like this?
{
"pokemon": {
"id": 2,
"name": "Squirtle",
"birthDate": "1903-01-01T00:00:00",
"pokemonCategories": [
{
"pokemonId": 2,
"categoryId": 2,
"category": {
"id": 2,
"name": "Water",
}
}
]
}
}
You can create DTO and return any data you want from that action. It's also recommended to use DTOs. Because sometimes you might not want to expose all properties in those classes. For example, let's say you have a DTO like this;
public class PokemonDto
{
public Pokemon Pokemon { get; set; }
public Category Category { get; set; }
}
Then you can get Pokémon and its category then return it from the action like this;
public async Task<IActionResult> GetPokemons(int pokemonId, int categoryId)
{
var pokemon = await _databaseContext.Pokemons.FirstOrDefaultAsync(p => p.Id == pokemonId);
var category = await _databaseContext.Categories.FirstOrDefaultAsync(c => c.Id == categoryId);
return Ok(new PokemonDto
{
Pokemon = pokemon,
Category = category
});
}

Automapper inner model attribute returns null

so I have an entity model using Entity Framework Core which is used in ASP.NET Core Web API.
I have created two entity models:
public class FishCategory
{
[Key]
[Required]
public int ID { get; set; }
[Required]
[MaxLength(50)]
public string Category { get; set; }
public IEnumerable<Fish> Fish { get; set; }
}
public class Fish
{
[Key]
public int ID { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
[MaxLength(100)]
public string Description { get; set; }
[Required]
public float WeightKg { get; set; }
[Required]
public int Stock { get; set; }
[Required]
public float Price { get; set; }
[Required]
public int CategoryID { get; set; }
public FishCategory Category { get; set; }
public int UserID { get; set; }
public User Seller { get; set; }
public Item Item { get; set; }
}
These 2 entities have one to many relationship
Then here is the 2 DTO models for both of the entities
public class FishReadDTO
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public float WeightKg { get; set; }
public int Stock { get; set; }
public float Price { get; set; }
public FishCategoryReadDTO Category { get; set; }
public UserReadDTO Seller { get; set; }
}
public class FishCategoryReadDTO
{
public int ID { get; set; }
public string Category { get; set; }
}
Here the repository for the Fish entity model
public Fish GetFishById(int id)
{
return _databaseContext.Fishes
.Where(fish => fish.ID.Equals(id))
.Select(fish => new Fish()
{
ID = fish.ID,
Category = fish.Category,
CategoryID = fish.CategoryID,
Description = fish.Description,
Item = fish.Item,
Name = fish.Name,
Price = fish.Price,
Seller = fish.Seller,
Stock = fish.Stock,
UserID = fish.UserID,
WeightKg = fish.WeightKg
}).FirstOrDefault();
}
Here's the Profile class for the DTO
public class EFishingProfile : Profile
{
public EFishingProfile()
{
CreateMap<User, UserReadDTO>();
CreateMap<FishCategory, FishCategoryReadDTO>();
CreateMap<Fish, FishReadDTO>();
CreateMap<FishCreateDTO, Fish>();
}
}
Here's the controller that executes this method to get the data
[HttpGet("{id}", Name = "GetFishById")]
public ActionResult<FishReadDTO> GetFishById(int id)
{
var fish = _repository.GetFishById(id);
if(fish == null)
{
return NotFound();
}
return Ok(_mapper.Map<FishReadDTO>(fish));
}
[HttpPost]
public ActionResult<Fish> CreateFish(FishCreateDTO fishCreateDTO)
{
var fishModel = _mapper.Map<Fish>(fishCreateDTO);
_repository.CreateFish(fishModel);
var fishRead = _mapper.Map<FishReadDTO>(fishModel);
return CreatedAtRoute(nameof(GetFishById), new {Id = fishRead.ID}, fishRead);
}
The result of posting a "Fish" data returns me a "Fish" entity model mapped to the "FishReadDTO" model but I would like to map it to the FishReadDTO but it return me a null value for property of "category" and "fish" in the Fish DTO model itself. Here is an example of it
{
"id": 12,
"name": "Test 3",
"description": "Test 3",
"weightKg": 76.25,
"stock": 10,
"price": 50.0,
"category": null,
"seller": null
}
Here's what I want which uses the FishDTO when getting a single "Fish" entity model mapped to the DTO which is working via the API method called "GetFishById(int id)". You can refer the method in my controller code posted.
{
"id": 9,
"name": "Northern Pike",
"description": "A predatorial fish found in the waters of canal and rivers",
"weightKg": 76.25,
"stock": 10,
"price": 50.0,
"category": {
"id": 1,
"category": "Salt Water/Sea Fish"
},
"seller": {
"id": 1,
"username": "AdrianJ",
"name": "Adrian Joseph",
"email": "adrianjoseph#efishing.com"
}
}
Is there any way to basically get the "category" and "seller" being mapped and not getting null?
**Note that while using the "GetByFishId(int id)" API method which invoked my "FishRepository", without using the ".Include(...)" method in the repository I still get the values for "category" and "seller", the issue is at the API method where I create/post a new Fish which is this method"
[HttpPost]
public ActionResult<Fish> CreateFish(FishCreateDTO fishCreateDTO)
" into my database and it returns me a 201 Created using "CreatedAtRoute" is when I received the response of a "FishReadDTO" with null values of "category" and "seller" only.**
You need to fetch relations from the database. When you load an entity from the DbContext, its relations are not loaded, so you get null.
In your repository, if you change
_databaseContext.Fishes
.Where(fish => fish.ID.Equals(id))
to
_databaseContext.Fishes
.Include(e => e.Category)
.Include(e => e.Seller)
.FirstOrDefault(fish => fish.ID.Equals(id))
then DbContext will load those relations and navigation properties Category and Seller will be populated. Then AutoMapper can map those objects to DTOs like you expect.
For reference, see docs

Flattening complex data into a single object

I am trying to abstract data from a complex and create a EventDto.
And I was able to do it using foreach but the syntax is dreadful.
Is there a better way of writing this code?
public class EventDtO
{
public string Id { get; set; }
public string Title { get; set; }
public string CategoryTitle { get; set; }
public DateTime DateTime { get; set; }
}
This is the complex object that i am trying to get the data from
public class RootObject
{
public List<Event> Events { get; set; }
}
public class Event
{
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Link { get; set; }
public List<Category> Categories { get; set; }
public List<Geometry> Geometries { get; set; }
}
public class Geometry
{
public DateTime Date { get; set; }
public string Type { get; set; }
public List<object> Coordinates { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Title { get; set; }
}
The mapping relationship i want is
EventDto.Id->Event.Id
EventDto.Title->Event.Title
Event.CategoryTitle->Category.Title
Event.DateTime->Geometry.Date
The Category class will only contain one value, but the geometry.Date can have multiple values.
So the output i want is:
Title Categories Date
"Iceberg B42" Sea and Lake Ice 2020-04-23T14:24:00Z
"Iceberg B42" Sea and Lake Ice 2017-09-15T00:00:00Z
I am able to get the correct information if i do the following code.
var Event = new List<EventDTO>();
foreach (var con in content.Events)
{
var data = new EventDTO
{
Title = con.Title,
Id = con.Id
};
foreach (var cat in con.Categories)
{
data.CategoriesTitle = cat.Title;
}
foreach (var geo in con.Geometries)
{
data.DateTime = geo.Date;
Event.Add(data);
}
}
An example of the json
{
"id": "EONET_2881",
"title": "Iceberg B42",
"description": "",
"categories": [
{
"id": 15,
"title": "Sea and Lake Ice"
}
]
"geometries": [
{
"date": "2017-04-21T00:00:00Z",
"type": "Point",
"coordinates": [ -107.19, -74.63 ]
},
{
"date": "2017-09-15T00:00:00Z",
"type": "Point",
"coordinates": [ -107.11, -74.08 ]
}
]
}
You weren't creating a new EventDTO for each Geometry. Wouldn't this lead to multiple records with the date of the last one? Is this what you are looking for;
var Event = content.Events.SelectMany(con =>
con.Geometries.Select(geo =>
new EventDTO
{
Title = con.Title,
Id = con.Id,
CategoriesTitle = con.Categories.FirstOrDefault().Title,
DateTime = geo.Date
})
).ToList();

What is the best way to desirialize this string of json?

I was wondering how I could at best deserialize this json string. For doing that I am using Newtonsoft.json as a plugin with Xamarin.
I only need the parts that says "transaction" and the array in it "transactions" in a list.
{
"id": 999,
"transactions": [
{
"order": 1,
"displayName": "01_lgn",
"transaction": {
"id": 7791,
"name": "01_lgn",
"description": null,
"warning": 1,
"poor": 2,
"timeOut": 45,
"tolerated": 3,
"frustrated": 7,
"state": 1,
"includeInThroughputCalculation": true
}
}
{
"order": 2,
"displayName": "02",
"transaction": {
"id": 7793,
"name": "02",
"description": null,
"warning": 1,
"poor": 2,
"timeOut": 45,
"tolerated": 3,
"frustrated": 7,
"state": 1,
"includeInThroughputCalculation": true
}
}
],
"defies": null,
"state": 1,
"reportDisplayName": "testSomething"
}
What I already have tried is to put it in a strongly typed class and then make a list of it.
public class testTransaction
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("state")]
public int State { get; set; }
[JsonProperty("reportDisplayName")]
public string ReportDisplayName { get; set; }
[JsonProperty("transactions")]
public List<testTransactions> testTransactions { get; set; }
}
public class testTransactions
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public object Description { get; set; }
[JsonProperty("warning")]
public int Warning { get; set; }
[JsonProperty("poor")]
public int Poor { get; set; }
[JsonProperty("timeOut")]
public int TimeOut { get; set; }
[JsonProperty("tolerated")]
public int Tolerated { get; set; }
[JsonProperty("frustrated")]
public int Frustrated { get; set; }
[JsonProperty("state")]
public int State { get; set; }
[JsonProperty("includeInThroughputCalculation")]
public bool IncludeInThroughputCalculation { get; set; }
}
But when I try to deserialize it in this way the "searchResult" is empty and I see that nothing is added to the list.
var bleh = jsonstring;
JObject parsedketenObject = JObject.Parse(bleh);
IList<JToken> jTokenResults1 = parsedketenObject;
IList<JToken> jTokenResults2 = parsedketenObject ["transactions"].Children ().ToList ();
IList<JToken> jTokenResults3 = parsedketenObject["transactions"][0]["transaction"].Children().ToList();
_Transactions_list = new List<testTransaction>();
foreach (JToken result in jTokenResults2)
{
testTransaction searchResult = JsonConvert.DeserializeObject<testTransaction>(result.ToString());
_Transactions_list.Add(searchResult);
}
Firstly, the JSON seems to be malformed, you are missing comma.
{
"id": 999,
"transactions": [
{
"order": 1,
"displayName": "01_lgn",
"transaction": {
"id": 7791,
"name": "01_lgn",
"description": null,
"warning": 1,
"poor": 2,
"timeOut": 45,
"tolerated": 3,
"frustrated": 7,
"state": 1,
"includeInThroughputCalculation": true
}
}, <-------- HERE
{
"order": 2,
"displayName": "02",
"transaction": {
"id": 7793,
"name": "02",
"description": null,
"warning": 1,
"poor": 2,
"timeOut": 45,
"tolerated": 3,
"frustrated": 7,
"state": 1,
"includeInThroughputCalculation": true
}
}
],
"defies": null,
"state": 1,
"reportDisplayName": "testSomething"
}
Additionally, try these for your POCO instead:
public class TransactionDetails
{
public int id { get; set; }
public string name { get; set; }
public object description { get; set; }
public int warning { get; set; }
public int poor { get; set; }
public int timeOut { get; set; }
public int tolerated { get; set; }
public int frustrated { get; set; }
public int state { get; set; }
public bool includeInThroughputCalculation { get; set; }
}
public class Transaction
{
public int order { get; set; }
public string displayName { get; set; }
public TransactionDetails transaction { get; set; }
}
public class RootObject
{
public int id { get; set; }
public List<Transaction> transactions { get; set; }
public object defies { get; set; }
public int state { get; set; }
public string reportDisplayName { get; set; }
}
Then you can use
var x = JsonConvert.DeserializeObject<RootObject>(blah);
x will contain transactions, which is already a list.
This method is not strongly typed, but it will work:
var jsonString = GetTheJson(); // however you get your json
dynamic jsonObject = JsonConvert.DeserializeObject(jsonString);
foreach (var txn in jsonObject.transactions)
{
Console.WriteLine("{0} {1} {2}", txn.order, txn.displayName, txn.transaction.id);
}
The best way to serialize/deserialize a json string to json object is using Google Gson library. You should create DTO files and use its type to serialize the string.
The documentation can be found here: https://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/Gson.html

Formatting Serialized JSON With Code-First Entity Framework

I'm using a Web-API GET to return JSON for a Report.
The GET method returns a simple db.Reports.ToList();
This is the dump of the data I retrieve
{
"Project": {
"Location": {
"LocationId": 7,
"Description": "New York"
},
"Department": {
"DepartmentId": 7,
"Description": "Engineering"
},
"ProjectId": 7,
"Description": "Project_3",
"LocationId": 7,
"DepartmentId": 7
},
"Person": {
"Email": "email#gmail.com",
"FirstName": "John",
"LastName": "Doe",
"IsActive": true
},
"StatusCode": {
"StatusId": 8,
"Description": "Accepted"
},
"ReportId": "d4cddb3f-ea6a-4b0a-9820-19bd8ee43b3a",
"Description": "Report 3",
"RoundTrip": 45.88,
"IsBillable": true,
"StartDate": "2013-06-27T00:00:00",
"EndDate": "2013-06-27T14:36:32.467",
"TimeUpdated": "AAAAAAAAJxM="
}, ...
}
This is the related Report declaration:
public class Report
{
public Guid ReportId { get; set; }
public string Description { get; set; }
public double RoundTrip { get; set; }
public bool IsBillable { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public virtual Project Project { get; set; }
public byte[] TimeUpdated { get; set; }
public virtual Person Person { get; set; }
public virtual StatusCode StatusCode { get; set; }
}
In this situation, I'd really like to just have the Ids of the various objects contained in the Report class. For example, I'd really just like to see:
"Project": 7,
"Location": 7,
"Department": 7,
"Person": "email#gmail.com",
"StatusCode": 8,
"ReportId": "d4cddb3f-ea6a-4b0a-9820-19bd8ee43b3a",
"Description": "Report 3",
"RoundTrip": 45.88,
"IsBillable": true,
"StartDate": "2013-06-27T00:00:00",
"EndDate": "2013-06-27T14:36:32.467",
"TimeUpdated": "AAAAAAAAJxM="
Is there a relatively easy way to go about doing this, or would it be in my better interests to just further parse the result I'm seeing already?
Why does EF by default create these objects within the JSON rather than just the foreign keys?
You can do without specifically creating a new class. In your ApiController, if you are using the typed return type (in favor of an HttpResponseMessage), change the List type to IEnumerable<object> and return:
return db.Reports.Select(r => new {
Project = r.ProjectId;
Location = r.Location.LocationId;
Department = r.Department.DepartmentId;
Person = r.Person.Email;
StatusCode = r.StatusCode.StatusId;
Description: r.Description
RoundTrip: r.RoundTrip
IsBillable: r.IsBillable,
StartDate: r.StartDate,
EndDate: r.EndDate
TimeUpdated: r.TimeUpdated
});
// Or if you're using HttpResponseMessage
return Request.CreateResponse(HttpStatusCode.Ok,
db.Reports.Select(r => new {
Project = r.ProjectId;
Location = r.Location.LocationId;
Department = r.Department.DepartmentId;
Person = r.Person.Email;
StatusCode = r.StatusCode.StatusId;
Description: r.Description
RoundTrip: r.RoundTrip
IsBillable: r.IsBillable,
StartDate: r.StartDate,
EndDate: r.EndDate
TimeUpdated: r.TimeUpdated
}));
The default Json serializer (Newtonsoft's Json.Net) is smart enough to serialize anonymous object. The only unknown in the code above is the behaviour of the TimeUpdated member as it's a byte array. You may have to adjust the assignment.
I would recommend making a model for displaying the JSON as you want it to be displayed. This would be the easiest option.
Something like this should work:
public class ReportSimple
{
public Guid ReportId { get; set; }
public int Project { get; set; }
public int Location { get; set; }
public int Department { get; set; }
public string Person { get; set; }
public int StatusCode { get; set; }
public string Description { get; set; }
public double RoundTrip { get; set; }
public bool IsBillable { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public byte[] TimeUpdated { get; set; }
public ReportSimple(Project project, Person person, StatusCode statusCode)
{
Project = project.ProjectId;
Location = project.Location.LocationId;
Department = project.Department.DepartmentId;
Person = person.Email;
StatusCode = statusCode.StatusId;
}
}

Categories

Resources