C
C#10mo 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
Angius10mo 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
eysidi10mo ago
I see, I got confused because every tutorial has a different approach of declaring relation
Angius
Angius10mo 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
eysidi10mo ago
Yes it is 1 to 1
Angius
Angius10mo ago
What many tutorials fail at, however, is they all use .Include() instead of .Select()ing into DTOs
eysidi
eysidi10mo ago
so what am I supposed to do here? so, select?
Angius
Angius10mo 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
eysidi10mo ago
there are some differences but even in official docs they use include
eysidi
eysidi10mo 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
eysidi10mo 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
Angius10mo ago
Probably because it's a doc about loading related data While .Select() is a projection And it also loads related data
eysidi
eysidi10mo ago
meaning?
Angius
Angius10mo 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
eysidi10mo ago
So Select is annotation?
Angius
Angius10mo ago
huh? No?
eysidi
eysidi10mo ago
ah I see you are also flattening it as you said
Angius
Angius10mo ago
It's a LINQ method that projects each element of an enumerable to a different thing
eysidi
eysidi10mo ago
what about without flattening?
Angius
Angius10mo 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
eysidi10mo ago
Preserve the nested structure?
Angius
Angius10mo ago
Yep
eysidi
eysidi10mo 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
Angius10mo 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
eysidi10mo 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
Angius10mo ago
It's for lazy loading Don't use lazy loading
eysidi
eysidi10mo ago
I see, thanks a lot for your help
Pobiega
Pobiega10mo ago
Also, don't return your database entitites directly
Accord
Accord10mo 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
More Posts