2

I am a little confused with Lazy Loading. I was under the impression that it loads navigational properties when they are accessed, but in my code it seems to try to pull it all in. This could be due to my Service/Repository pattern, but at the moment I am getting circular references :(

When I called my service like this:

using (var service = new UserService(new CompanyService()))
{
    var u = await service.GetAll();                  

    return new JsonResult { Data = new { success = true, users = u } }; // Return our users
}

it is bringing in a list of User

public partial class User : IdentityUser
{
    public User()
    {
        // ...
        this.MemberOf = new List<Group>();
        // ...
    }

    // ...

    // ...
    public virtual ICollection<Group> MemberOf { get; set; }
    // ...
}

Which then seems to bring in a list of Group

public partial class Group
{
    public Group()
    {
        // ...
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    // ...

    // ...
    public virtual Company Company { get; set; }
    // ...
}

which then brings in a Company

public partial class Company
{
    public Company()
    {
        this.Assets = new List<Asset>();
        // ..
    }

    public string Id { get; set; }
    public string Name { get; set; }
    // ..
            
    // ...
    public virtual ICollection<Asset> Assets { get; set; }
    // ...
}

which brings in a list of Asset

public partial class Asset
{
    public Asset()
    {
        // ...
        this.Categories = new List<Category>();
        // ...
    }

    public int Id { get; set; }
    public string FileName { get; set; }
    public string ThumbNail { get; set; }
    // ...

    // ...
    public virtual ICollection<Category> Categories { get; set; }
    // ...
}

which brings in a list of Category and this is where the circular reference happens because it is bringing in a list of Asset which is bring in a list of Category and so on.

I thought that using Lazy loading, it would only bring in the Users and their navigational properties and that is it, unless I tell it otherwise?

I tried just using this method instead of my service (just to test);

var u = new SkipstoneContext().Users;

return new JsonResult { Data = new { success = true, users = u } }; // Return our users

but I still get the circular reference.

Can someone explain to me why it is trying to load all navigational properties and if there is something I can (easily) do to stop it?

Update 2

Keith suggested using Interfaces and a ContractResolver to help with the serialisation, so this is what I did.

First, I created 2 new interfaces:

public interface IBaseUser
{
    string Id { get; set; }
    string UserName { get; set; }
    string Email { get; set; }
    bool IsApproved { get; set; }
    bool IsLockedOut { get; set; }

    ICollection<Group> MemberOf { get; set; }
}

and

public interface IBaseGroup
{
    int Id { get; set; }
    string Name { get; set; }
}

and the Models implemented these classes

public partial class User : IdentityUser, IBaseUser

and

public partial class Group : IBaseGroup

so that was the first step, the second step was to create the ContractResolver class which looks like this:

public class InterfaceContractResolver : DefaultContractResolver
{
    private readonly Type interfaceType;

    public InterfaceContractResolver(Type interfaceType)
    {
        this.interfaceType = interfaceType;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(this.interfaceType, memberSerialization);

        return properties;
    }
}

and my method for getting the users now looks like this:

public async Task<JsonNetResult> Get()
{
    try
    {
        using (var service = new UserService(new CompanyService()))
        {
            var u = await service.GetAll();

            var serializedObject = JsonConvert.SerializeObject(u,
                new JsonSerializerSettings()
                {
                    ContractResolver = new InterfaceContractResolver(typeof(IBaseUser))
                });

            return new JsonNetResult { Data = new { success = true, users = serializedObject } }; // Return our users
        }
    }
    catch (Exception ex)
    {
        return new JsonNetResult { Data = new { success = false, error = ex.Message } };
    }
}

So, when this code runs I get an error:

Unable to cast object of type 'System.Data.Entity.DynamicProxies.Group_D0E52FCCF207A8F550FE47938CA59DEC7F963E8080A64F04D2D4E5BF1D61BA0B' to type 'Skipstone.Web.Identity.IBaseUser'.

which makes sense because the InterfaceContractResolver only expects on interface type.

The question is how would I get around that?

Update 3

Ok, this is getting silly now.

So I disabled Lazy Loading by doing this:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.Configuration.LazyLoadingEnabled = false; // Disable Lazy Loading

        // ...
    }

And then I created this repository:

public abstract class Repository<TEntity> : IDisposable, IRepository<TEntity> where TEntity : class
{
    public DbContext Context { get; private set; }
    public IQueryable<TEntity> EntitySet { get { return this.DbEntitySet; } }
    public DbSet<TEntity> DbEntitySet { get; private set; }

    public Repository(DbContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        this.Context = context;
        this.DbEntitySet = context.Set<TEntity>();
    }

    public Task<TEntity> GetAsync(object id)
    {
        return this.DbEntitySet.FindAsync(new object[]
        {
            id
        });
    }

    public IEnumerable<TEntity> GetAll()
    {
        return this.DbEntitySet;
    }

    public IEnumerable<TEntity> GetAll(string include)
    {
        return this.DbEntitySet.Include(include);
    }

    public IEnumerable<TEntity> GetAll(string[] includes)
    {
        foreach (var include in includes)
            this.DbEntitySet.Include(include);

        //var t = this.DbEntitySet.Include("Settings").ToList();

        // return this.GetAll();
        return this.DbEntitySet;
    }

    public void Add(TEntity model)
    {
        this.DbEntitySet.Add(model);
    }

    public void Remove(TEntity model)
    {
        this.Context.Entry<TEntity>(model).State = EntityState.Deleted;
    }

    public void Dispose()
    {
        this.Context.Dispose();
    }
}

which my UserRepository inherits:

public class UserRepository : Repository<User>
{
    public UserRepository(DbContext context)
        : base(context)
    {
    }
}

Then my service has this method:

    public IList<User> GetAll(string include)
    {
        return this.repository.GetAll(include).Where(model => model.CompanyId.Equals(this.companyId, StringComparison.OrdinalIgnoreCase)).ToList();
    }

and now my JsonResult method looks like this:

    // 
    // AJAX: /Users/Get

    public JsonResult Get()
    {
        try
        {
            using (var service = new UserService(new CompanyService()))
            {
                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 } };
        }
    }

and guess what?!?!? If I put a breakpoint just before the return I see that u has it's properties all populated and the navigation properties are all not set except MemberOf which is exactly what I want (for now) but when I step over the return I get the same error as before!!!!

An exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll but was not handled in user code

Additional information: Self referencing loop detected with type 'System.Data.Entity.DynamicProxies.Asset_AED71699FAD007BF6F823A5F022DB9888F62EBBD9E422BBB11D7A191CD784288'. Path 'users[0].Company.Assets[0].Categories[0].Assets'.

How/Why is this possible?

Update 4

This appears to because the properties were still marked with Virtual. When I removed virtual I stopped getting the error for the Assets, instead I now get it for the CreatedBy property.

This is my User class:

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; }
}

Here is the error:

An exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll but was not handled in user code

Additional information: Self referencing loop detected for property 'CreatedBy' with type 'System.Data.Entity.DynamicProxies.User_E9B58CAAA82234358C2DE2AF8788D33803C4440F800EA8E015BE49C58B010EDF'. Path 'users[0]'.

I don't understand HOW the CreatedBy property is being realised because it is not a virtual property and I do not ask for it using Eager or Explicit loading......

Does anyone know why?

Community
  • 1
  • 1
r3plica
  • 13,017
  • 23
  • 128
  • 290
  • `JsonNetResult` actually access each of your property and converts it to a JSON object. – Mat J Jan 08 '14 at 11:49
  • I suspect that serializing the entity to a json result causes it to eager load the whole object graph. To confirm this, debug the app while SQL Profiler is running and set a breakpoint on the return statement. See if it has loaded all of the data before executing the statement. Then see if it has loaded all of the data afterwards. – Keith Payne Jan 08 '14 at 11:49
  • Can stop it serializing the json by sticking `[JsonIgnore]` attribute on the navigation property – Thewads Jan 08 '14 at 12:39
  • another way would be implementing ViewModels. – Guillermo Oramas R. Jan 08 '14 at 13:24
  • @KeithPayne you are correct. I did a Trace and I could see that at first it just pulls in all the users, then it eager loads everything after I try to serialise it. How can I stop it doing that? – r3plica Jan 08 '14 at 14:39
  • 1
    @Guillelon I would prefer not to use ViewModels because it defeats the object of having the navigation properties. If I use ViewModels I would have to create multiple Models for each POCO depending on which navigation property I wish to be available surely? If that was the case, then it would make more sense to use eager loading. What I don't understand is why is it even trying to eager load everything. – r3plica Jan 08 '14 at 14:42
  • Yes, you'll need to create each POCO you need, viewModels implementations are tedious but makes your code look simpler and ordered. What its happening is what @Mathew said, and the solution that @Thewards provided about `[JsonIgnore]` on the properties that you don't want to be serialized (and so the navigation stops) works but you may need this properties to be serialized in another feature. Check this -> http://stackoverflow.com/questions/12110217/where-should-i-add-jsonignore-to-prevent-certain-properties-from-being-seriali – Guillermo Oramas R. Jan 08 '14 at 15:03
  • It is not eager loading anything, your serializer is explicitly asking for everything to be loaded. Serializing it is what _causes_ the loading, it is lazy loading, but how can a serializer serialize data it doesn;t have? It will simply trigger the data to be loaded. – oerkelens Jan 09 '14 at 13:09
  • how can i tell it not to? I mean they are just null collections/properties surely it will just show it as an empty collection. That is the way it used to work before EF – r3plica Jan 09 '14 at 14:54

2 Answers2

1

The JSON serialization is achieved internally by using reflection. So it tries to access every public property you have to build the JSON result.

You can add the [JsonIgnore] attribute on navigation properties you don't want to be serialized. This could also be useful to prevent the serialization to produce circular references (i.e. for many to many relationship for example).

That being said, I would recommend to use ViewModels instead of the entities. Sooner or later you'll need to add other properties than the properties directly coming from the entity itself (for example: read only computed field, other field not related to your entity but required in your Form...etc.). You could add those properties in a partial definition of the entity, but when you'll have many views it won't be maintainable/readable anymore.

Second point is, unless you really need it for a specific reason, I would recommend deactivating lazy-loading for three main reasons:

  • you could have serious performance issues if you're not very familiar with your ORM (you could google for "extra lazy loading" for instance)
  • extremely hard to handle exceptions, as every part of your code might throw an exception related to a failure in the SQL request execution, not just the part that queries entities from the database context itself.
  • lazy loading tends to lengthen the context lifetime (as you can't dispose it until you're sure you'll never lazy load a property)
ken2k
  • 48,145
  • 10
  • 116
  • 176
  • When I posted this, I decided to have a go at Eager Loading. It involved changing my repositories to suit. Would you say Eager Loading is better (performance wise) than Lazy Loading? – r3plica Jan 08 '14 at 17:51
  • @r3plica It's hard to provide a general answer to this question, but I think I personally won't use lazy loading anymore. It's just too hard to handle exceptions correctly, and it's just too easy to produce awful code that executes thousands of queries against the DB instead of one (resulting in extremely poor performances). Of course you can write good code AND activate lazy loading, but it's just too painful IMO. – ken2k Jan 08 '14 at 17:55
0

This might work:

Create one or more interfaces that define the properties that you want to serialize. One interface per "snapshot" of the entity.

Have your EF entity implement all of the interfaces.

Then create a contract resolver that works only on the properties that are defined on an interface type that is passed to the resolver. See Serialize only interface properties to JSON with Json.net and use the answer with contract resolver code.

Once you have the contract resolver coded, you can pass the EF entity and whichever interface you like.

Hopefully the serializer will ignore the properties that the contract resolver does not "see".

If this works, please post your own answer with your working code and mark it as the answer.

Community
  • 1
  • 1
Keith Payne
  • 3,002
  • 16
  • 30