C
C#16mo ago
eysidi

❔ Serializing related object EF

Hello all, I have 2 models
public class City {
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string CountryName { get; set; }
public Coordinates? Coordinates { get; set; }
}

public class Coordinates {
public int Id { get; set; }
public double X { get; set; }
public double Y { get; set; }
public int CityId { get; set; }
public City City { get; set; } = null!;
}
public class City {
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string CountryName { get; set; }
public Coordinates? Coordinates { get; set; }
}

public class Coordinates {
public int Id { get; set; }
public double X { get; set; }
public double Y { get; set; }
public int CityId { get; set; }
public City City { get; set; } = null!;
}
And I would like to include Coordinates of City in my response however I get error JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 64 My query is
return db.Cities
.Where(c => c.Name.ToLower().Contains(city.ToLower()))
.Include(c => c.Coordinates)
.ToList();
return db.Cities
.Where(c => c.Name.ToLower().Contains(city.ToLower()))
.Include(c => c.Coordinates)
.ToList();
I have googled it and found different approaches but from the examples I saw, this should be working. Why does this happen?
28 Replies
Angius
Angius16mo ago
Well, City references Coordinates and Coordinates reference the City So we do have an endless loop .Select() into a DTO instead of .Include()ing The same ringts true for all cases Even ones without cyclic dependencies
eysidi
eysidiOP16mo ago
I see, I got confused because every tutorial has a different approach of declaring relation
Angius
Angius16mo ago
You could even flatten it too, since it's a 1:1 relationship If it's supposed to be a 1:1 relationship, then it's declared correctly
eysidi
eysidiOP16mo ago
Yes it is 1 to 1
Angius
Angius16mo ago
What many tutorials fail at, however, is they all use .Include() instead of .Select()ing into DTOs
eysidi
eysidiOP16mo ago
so what am I supposed to do here? so, select?
Angius
Angius16mo ago
Yes
return await db.Cities
.Where(c => c.Name.ToLower().Contains(city.ToLower()))
.Select(c => new CityDto {
Name = c.Name,
Country = c.CountryName
X = c.Coordinates.X
Y = c.Coordinates.Y
})
.ToListAsync();
return await db.Cities
.Where(c => c.Name.ToLower().Contains(city.ToLower()))
.Select(c => new CityDto {
Name = c.Name,
Country = c.CountryName
X = c.Coordinates.X
Y = c.Coordinates.Y
})
.ToListAsync();
for example
eysidi
eysidiOP16mo ago
there are some differences but even in official docs they use include
eysidi
eysidiOP16mo ago
Tutorial: Read related data - ASP.NET MVC with EF Core
In this tutorial you'll read and display related data -- that is, data that the Entity Framework loads into navigation properties.
eysidi
eysidiOP16mo ago
not the same
public async Task<IActionResult> Index()
{
var courses = _context.Courses
.Include(c => c.Department)
.AsNoTracking();
return View(await courses.ToListAsync());
}
public async Task<IActionResult> Index()
{
var courses = _context.Courses
.Include(c => c.Department)
.AsNoTracking();
return View(await courses.ToListAsync());
}
from docs
Angius
Angius16mo ago
Probably because it's a doc about loading related data While .Select() is a projection And it also loads related data
eysidi
eysidiOP16mo ago
meaning?
Angius
Angius16mo ago
Well, a chapter in the math book about addition won't cover multiplication Even if 2 + 2 + 2 + 2 could be better represented as 2 * 4
eysidi
eysidiOP16mo ago
So Select is annotation?
Angius
Angius16mo ago
huh? No?
eysidi
eysidiOP16mo ago
ah I see you are also flattening it as you said
Angius
Angius16mo ago
It's a LINQ method that projects each element of an enumerable to a different thing
eysidi
eysidiOP16mo ago
what about without flattening?
Angius
Angius16mo ago
return await db.Cities
.Where(c => c.Name.ToLower().Contains(city.ToLower()))
.Select(c => new CityDto {
Name = c.Name,
Country = c.CountryName,
Coordinates = new CoordinatesDto {
X = c.Coordinates.X,
Y = c.Coordinates.Y
}
})
.ToListAsync();
return await db.Cities
.Where(c => c.Name.ToLower().Contains(city.ToLower()))
.Select(c => new CityDto {
Name = c.Name,
Country = c.CountryName,
Coordinates = new CoordinatesDto {
X = c.Coordinates.X,
Y = c.Coordinates.Y
}
})
.ToListAsync();
eysidi
eysidiOP16mo ago
Preserve the nested structure?
Angius
Angius16mo ago
Yep
eysidi
eysidiOP16mo ago
Yep that worked, What changes do I need to make my model so
return db.Cities
.Where(c => c.Name.ToLower().Contains(city.ToLower()))
.Include(c => c.Coordinates)
.ToList();
return db.Cities
.Where(c => c.Name.ToLower().Contains(city.ToLower()))
.Include(c => c.Coordinates)
.ToList();
this query works? Or is this wrong anyway?
Angius
Angius16mo ago
I'm not sure it can work, because of the cyclic dependency You'd have to either remove the reference to city from coordinates Or the reference to coordinates from city
eysidi
eysidiOP16mo ago
I will try yes now it works I removed City from Coordinates I have seen some examples where related model is declared as virtual
public class City {
...
public virtual Coordinates Coordinates { get; set; }
}
public class City {
...
public virtual Coordinates Coordinates { get; set; }
}
What does virtual do in this case? Which one is correct?
Angius
Angius16mo ago
It's for lazy loading Don't use lazy loading
eysidi
eysidiOP16mo ago
I see, thanks a lot for your help
Pobiega
Pobiega16mo ago
Also, don't return your database entitites directly
Accord
Accord15mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server