1

I have a WebAPI which calls functions from a Class Library which calls data from a Entity Framework Model First Class Library.

I am suffering from the dreaded "self referencing loop detected"

So, in my DataStore class library I have the autogenerated Context:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated from a template.
//
//     Manual changes to this file may cause unexpected behavior in your application.
//     Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace SADatastore
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;

public partial class SADatastoreEntities : DbContext
{
    public SADatastoreEntities()
        : base("name=SADatastoreEntities")
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        throw new UnintentionalCodeFirstException();
    }

    public virtual DbSet<DB> DBS { get; set; }
    ....
    }
}

I then have a Connections.cs file which I user to pass the connection string into the context with...

public partial class SADatastoreEntities
{
    public SADatastoreEntities(string connStr)
        : base(string.Format(@"metadata=res://*/SAModel.csdl|res://*/SAModel.ssdl|res://*/SAModel.msl;provider=System.Data.SqlClient;provider connection string='{0}'", connStr))
    {
        // http://stackoverflow.com/questions/12868996/permanently-disable-configuration-proxycreationenabled-in-ef
        Configuration.ProxyCreationEnabled = false;

    }
}

As you can see in the above code I am attempting to set ProxyCreationEnabled = false which is what I have read should solve my problem. But it has no effect.

In my API I have the following controller

public class SA_GetAllItemDetailsController : ApiController
{
    [Route("SA_GetAllItemDetails")]
    [HttpGet]
    public List<SA_Items> Get(DateTime? lastUpdated = null, int limit = 100, int offset = 0)
    {
        try
        {
            if (Helpers.Authorise(Request, "WriteVisitors").Authorised)
            {

                List<SA_Items> result = new List<SA_Items>();

                result = SADatastoreEngine.Functions.Item.SA_GetAllItemDetails(
                    Settings.Default.ConnectionString,
                    lastUpdated,
                    limit,
                    offset
                    );

                // conver the result to a string to have a look at it
                string json = JsonConvert.SerializeObject(result, Formatting.Indented);

                return result;
            }
            else
            {
                throw new HttpResponseException(Helpers.RespondNotAuthorised());
            }
        }
        catch (Exception e)
        {
            throw new HttpResponseException(Helpers.RespondWithAnError(e));
        }
    }
}
public class SA_GetItemByItemIdController : ApiController
{
    [Route("SA_GetItemByItemId/{id}")]
    [HttpGet]
    public SA_Items Get(string id)
    {
        try
        {
            if (Helpers.Authorise(Request, "WriteVisitors").Authorised)
                return SADatastoreEngine.Functions.Item.GetItemByItemId(Settings.Default.ConnectionString, id);
            else
            {
                throw new HttpResponseException(Helpers.RespondNotAuthorised());
            }
        }
        catch (Exception e)
        {
            throw new HttpResponseException(Helpers.RespondWithAnError(e));
        }
    }
}
public class SA_CreateItemController : ApiController
{
    [Route("SA_CreateItem")]
    [HttpPost]
    [ValidateViewModel]
    public Dictionary<string, string> Post(SA_Items item)
    {
        try
        {
            // get the auth details
            AuthorisationModel auth = Helpers.Authorise(Request, "WriteVisitors");

            if (Helpers.Authorise(Request, "WriteVisitors").Authorised)
            {
                return SADatastoreEngine.Functions.Item.CreateItem(Settings.Default.ConnectionString, auth.DBSId, item);
            }
            else
            {
                throw new HttpResponseException(Helpers.RespondNotAuthorised());
            }
        }
        catch (Exception e)
        {
            throw new HttpResponseException(Helpers.RespondWithAnError(e));
        }
    }
}
public class SA_UpdateItemController : ApiController
{
    [Route("SA_UpdateItem")]
    [HttpPut]
    public Dictionary<string, string> Post(SA_Items item)
    {
        try
        {
            // get the auth details
            AuthorisationModel auth = Helpers.Authorise(Request, "WriteVisitors");

            if (auth.Authorised)
            {
                return SADatastoreEngine.Functions.Item.UpdateItem(Settings.Default.ConnectionString, auth.DBSId, item);
            }
            else
            {
                throw new HttpResponseException(Helpers.RespondNotAuthorised());
            }
        }
        catch (Exception e)
        {
            throw new HttpResponseException(Helpers.RespondWithAnError(e));
        }
    }
}

Here is the SA_GetAllItemDetails function:

public static List<SA_Items> SA_GetAllItemDetails(string connStr, DateTime? lastUpdated, int limit, int offset) {
        using (SADatastoreEntities db = new SADatastoreEntities(connStr)) {
            if (lastUpdated == null)
                lastUpdated = new DateTime(1900, 1, 1);

            return db.SA_Items
                .Include("SA_Model")
                .Include("SA_Model.SA_Manufacturers")
                .Where(x => x.LastUpdated > lastUpdated)
                .OrderBy(x => x.ItemID)
                .Skip(offset)
                .Take(limit)
                .ToList();
        }
    }

I have added a line where I am attempting to convert the object to a json string so I can see it. But this is where it throws the error.

I have the feeling the setting

Configuration.ProxyCreationEnabled = false;

is being ignored because it's in the wrong location. But I cannot work out where it should be.

Can anyone see what I am doing wrong please?

EDIT

I have now changed my code to attempt to serialize it:

JsonSerializerSettings _jsonSettings = new JsonSerializerSettings()
{
      ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};

// convert the result to a string to have a look at it
string json = JsonConvert.SerializeObject(result, Formatting.Indented, _jsonSettings);

which leads to a Exception of type 'System.OutOfMemoryException' was thrown error

Trevor Daniel
  • 3,785
  • 12
  • 53
  • 89
  • So what's inside that SADatastoreEngine.Functions.Item.SA_GetAllItemDetails ? – Evk Mar 07 '17 at 10:30
  • @Evk added that code... sorry – Trevor Daniel Mar 07 '17 at 10:37
  • `SA_GetAllItemDetails` doesn't suffer from this problem but if you ever use `public SADatastoreEntities() : base("name=SADatastoreEntities")` you'll get the wrong behaviour - I think you intended to call `this("name=...")` – ta.speot.is Mar 07 '17 at 10:41

1 Answers1

0

The problem here is in Includes. When you retrieve SA_Items you include SA_Model which most likely has back reference to SA_Item. Because that SA_Item is already tracked by context (you make a query for them) - there is no need to use lazy loading or create any proxies to fill that back reference. In result, every SA_Item has reference to SA_Model which in turn has back reference to SA_Item no matter of LazyLoadingEnabled or ProxyCreationEnabled (because reference is already tracked by context).

You cannot prevent loading of that back references, so you have to manually check for loops when serializing.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • Sorry for my ignorance. How do you mean "you have to manually check for loops when serializing" I am using Newtonsoft.Json? – Trevor Daniel Mar 07 '17 at 11:02
  • @TrevorDaniel for example by using ReferenceLoopHandling.Ignore: http://www.newtonsoft.com/json/help/html/ReferenceLoopHandlingIgnore.htm – Evk Mar 07 '17 at 11:04
  • In my API I already have _config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; That has no effect either :( – Trevor Daniel Mar 07 '17 at 11:06
  • I don't see you pass any settings to JsonConvert in your example. Are you sure those settings from your config are indeed applied?. As far as I remember you need to use JsonConvert.DefaultSettings for that. – Evk Mar 07 '17 at 11:08
  • That could be the problem. That line appears to have no effect. I will need to look at what you mean by JsonConvert.DefaultSettings – Trevor Daniel Mar 07 '17 at 11:10
  • First try to pass settings explicitly to JsonConvert.SerializeObject which produces that loop error. If that resolves the problem you can see where is the problem with your current setup. – Evk Mar 07 '17 at 11:26
  • I get Exception of type 'System.OutOfMemoryException' was thrown. – Trevor Daniel Mar 07 '17 at 11:35
  • Are you use you use ReferenceLoopHandling.Ignore and not ReferenceLoopHandling.Serialize? Works fine for me in your case with ReferenceLoopHandling.Ignore. – Evk Mar 07 '17 at 11:40
  • yes. Just added my code to the question... (thanks for your help with this) – Trevor Daniel Mar 07 '17 at 11:45
  • 1
    Can't help anymore unfortunately, because cannot reproduce at this point (ReferenceLoopHandling.Ignore works for me). – Evk Mar 07 '17 at 12:58