Reputation: 13367
I am using Entity Framework (code first) and I have mapped all my entities and disabled lazy loading. This works fine, but as soon as I try to do some Ajax work I start to get issues.
The first issue is with my User class. This looks like this:
public partial class User : IdentityUser
{
public string CompanyId { get; set; }
public string CreatedById { get; set; }
public string ModifiedById { get; set; }
public System.DateTime DateCreated { get; set; }
public Nullable<System.DateTime> DateModified { get; set; }
public System.DateTime LastLoginDate { get; set; }
public string Title { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
public string JobTitle { get; set; }
public string Telephone { get; set; }
public string Mobile { get; set; }
public string Photo { get; set; }
public string LinkedIn { get; set; }
public string Twitter { get; set; }
public string Facebook { get; set; }
public string Google { get; set; }
public string Bio { get; set; }
public string CompanyName { get; set; }
public string CredentialId { get; set; }
public bool IsLockedOut { get; set; }
public bool IsApproved { get; set; }
public bool CanEditOwn { get; set; }
public bool CanEdit { get; set; }
public bool CanDownload { get; set; }
public bool RequiresApproval { get; set; }
public bool CanApprove { get; set; }
public bool CanSync { get; set; }
public bool AgreedTerms { get; set; }
public bool Deleted { get; set; }
public Company Company { get; set; }
public User CreatedBy { get; set; }
public User ModifiedBy { get; set; }
public ICollection<Asset> Assets { get; set; }
public ICollection<Category> Categories { get; set; }
public ICollection<Collection> Collections { get; set; }
public ICollection<Comment> Comments { get; set; }
public ICollection<LocalIntegration> LocalIntegrations { get; set; }
public ICollection<Page> Pages { get; set; }
public ICollection<Rating> Ratings { get; set; }
public ICollection<Theme> Themes { get; set; }
public ICollection<Group> MemberOf { get; set; }
public ICollection<Category> ForbiddenCategories { get; set; }
public ICollection<Page> ForbiddenPages { get; set; }
}
Now, because Lazy loading is disabled, I use Eager loading instead and if I have a look at this when I called this method:
//
// AJAX: /Users/Get
public JsonResult Get()
{
try
{
using (var uow = new UnitOfWork<SkipstoneContext>())
{
var service = new UserService(uow, User.Identity.GetUserId(), this.CompanyId);
var u = service.GetAll("MemberOf");
return new JsonResult { Data = new { success = true, users = u } }; // Return our users
}
}
catch (Exception ex)
{
return new JsonResult { Data = new { success = false, error = ex.Message } };
}
}
I can clearly see that only the Members navigation property is populated. The rest are null as expected.
If I try to pass u to my JsonResult, this is where the problem arises. I get the error:
Self referencing loop detected for property 'CreatedBy' with type 'System.Data.Entity.DynamicProxies.User_E9B58CAAA82234358C2DE2AF8788D33803C4440F800EA8E015BE49C58B010EDF'. Path 'users[0]'.
In another post, I read that this happens because Json.Net tries to serialise and populate all navigation properties.
So, I created a new project and in that I created 2 models:
public class User
{
public User()
{
this.Id = Guid.NewGuid().ToString();
}
public string Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string CreatedById { get; set; }
public ICollection<Post> Posts { get; set; }
public User CreatedBy { get; set; }
}
public class Post
{
public string Id { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public string CreatedById { get; set; }
public User CreatedBy { get; set; }
}
I would expect that running that, there would be a self referencing error because not only does User have many Post, it also has a User that created it.
So the method looks like this:
public JsonResult Get()
{
try
{
var user = new User()
{
UserName = "moo",
Email = "[email protected]"
};
return new JsonResult { Data = new { sucess = true, user = user } };
}
catch (Exception ex)
{
return new JsonResult { Data = new { sucess = true, error = ex.Message } };
}
}
again, this is identical (to my eyes) to the first project. Anyway, when I run this, the project runs fine.
Can someone explain to me why this is happening and what I can do to solve it?
Upvotes: 0
Views: 657
Reputation: 47375
I assume that your navigation properties are actually marked virtual
, otherwise EF won't try to lazy load them...
Just having a navigation property of the same type nested within an object's virtual properties does not make it self-referencing. When properties are null, empty collections, or when the JSON serializer can "find a way out" after walking through all of the entity's properties, then it is not self-referencing. Self-referencing means that I walk from User to Posts, then back to the same user, its posts again, and so on, infinitely.
To solve this, don't try to serialize entities. Instead, convert them first to viewmodels, then serialize the viewmodels.
public class UserViewModel
{
public string Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string CreatedById { get; set; }
public ICollection<PostViewModel> Posts { get; set; }
public string CreatedByUserName { get; set; } // end self-referencing
}
public class PostViewModel
{
public string Id { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public string CreatedById { get; set; }
public string CreatedByUserName { get; set; } // end self-referencing
}
public JsonResult Get()
{
try
{
using (var uow = new UnitOfWork<SkipstoneContext>())
{
var service = new UserService(uow, User.Identity.GetUserId(), this.CompanyId);
var u = service.GetAll("MemberOf");
var vm = Mapper.Map<UserViewModel[]>(u);
return new JsonResult { Data = new { success = true, users = vm } }; // Return our users
}
}
catch (Exception ex)
{
return new JsonResult { Data = new { success = false, error = ex.Message } };
}
}
Upvotes: 1