Search code examples
c#asp.netasp.net-mvcentity-frameworkasp.net-identity

Web API with separate entity framework data layer


I have a web api that is setup with asp.net identity that is using the default VS15 template and a "defaultconnection" connection string. I added a class library to the solution and on that I added an entity framework ado.net model from my database. I added a connection string that was in the class library's app.config file to my web config.

Web.config connection strings on api:

<connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=MyDatabase;Initial Catalog=Dev;Integrated Security=True" providerName="System.Data.SqlClient" />
    <add name="IBPC_DevEntities" connectionString="metadata=res://*/IBPCDataModel.csdl|res://*/IBPCDataModel.ssdl|res://*/IBPCDataModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=MyDatabase;initial catalog=Dev;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
</connectionStrings>

For a test I'm trying to do a get request for user info from my api's controller:

[Authorize]
public HttpResponseMessage Get(int id)
{
    using (IBPC_DevEntities entity = new IBPC_DevEntities())
    {
        return Request.CreateResponse(HttpStatusCode.OK,
            entity.User.FirstOrDefault(u => u.NDBHUserID == id));
    }
}

When I send the request in Postman (with the authorization token) It responds with an InnerException that says "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection."

How can I use database first EF6 in it's own project and reference it from my API that's using defaultconnection to authenticate?


Solution

  • There's tons of middleware involved between the returned value from Get --a User object in the .Net runtime space-- and the received value at the consumer end of the API. One of the first things that happens is that the User object is serialized as Json.

    Although the Json serializer (Json.Net) is a pretty smart beast, it doesn't know the difference between loaded and not loaded navigation properties. So if your User has a property like Accounts (just an example), it will try to serialize that property too. This triggers lazy loading, giving rise to this infamous exception.

    A quick fix is to disable lazy loading:

    using (IBPC_DevEntities entity = new IBPC_DevEntities())
    {
        entity.Configuration.LazyLoadingEnabled = false;
        return Request.CreateResponse(HttpStatusCode.OK,
            entity.User.FirstOrDefault(u => u.NDBHUserID == id));
    }
    

    A better fix is to use a view model/DTO object:

        return Request.CreateResponse(HttpStatusCode.OK,
            entity.User.Where(u => u.NDBHUserID == id)
                  .Select(u => new UserDto { Name = u.Name, .... })
                  .FirstOrDefault());
    

    Then you have full control over what will be sent over the wire and no lazy loading surprises will happen.