1

Hoping someone can help me track down a very unrevealing problem. I have a project that uses Web API 2 and Entity Framework 6 on .NET Framework 4.7.2. The project has been working fine for years, we recently decided to incorporate a couple additional databases in our project. As it turned out EF6 doesn't support similarly named Classes in different Models. Their are some hacky workarounds, Custom Tool Name Spacing and other such things. Alternatively, MS and some other SO posts recommended moving to .NET Core and Core EF. An attempt was made to migrate but it turns out that it's more of a port/re-write because many EF6 features are deprecated EF Core. As it is we bailed and decided to completely re-approach. We rolled the code back and chased down a couple nuanced issues and everything appeared to be working, except for one Class/API call which was discovered before we published code.

    [CustomAuthorize(Roles = "admin, sales, parts")]
    [Route("api/Customer/Get")]
    [HttpPost]
    public MERP.Customer GetCustomerProfile([FromBody] Models.Generic.GuidValue _input)
    {
        MARQERPEntities ent = new MARQERPEntities();
        var cst = ent.Customers.FirstOrDefault(w => w.ID == _input.ID);
        return cst;
    }

Stepping thru the code, the cst variable gets populated with the database object and the return step is executed. However, the payload never arrives at the client. If I open Task Manager the IIS Express Worker Process keeps churning away until all the memory is consumed and the following error is returned. I have other API endpoints using the same code pattern and they work fine.

An unhandled exception of type 'System.AccessViolationException' occurred in Unknown Module. Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

I'm not sure how to proceed here. I've blown away the EDMX and started over, I've removed and re-added the Customer class. The other Database Entities have been removed. I've compared our current packages.config to the pre EF Core Change Set and they're the same.

Here's the packages.config

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="EntityFramework" version="6.1.3" targetFramework="net472" />
  <package id="jQuery" version="3.1.1" targetFramework="net472" />
  <package id="Microsoft.AspNet.Cors" version="5.2.7" targetFramework="net472" />
  <package id="Microsoft.AspNet.Mvc" version="5.2.7" targetFramework="net472" />
  <package id="Microsoft.AspNet.Razor" version="3.2.7" targetFramework="net472" />
  <package id="Microsoft.AspNet.WebApi" version="5.2.7" targetFramework="net472" />
  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.7" targetFramework="net472" />
  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.7" targetFramework="net472" />
  <package id="Microsoft.AspNet.WebApi.Cors" version="5.2.7" targetFramework="net472" />
  <package id="Microsoft.AspNet.WebApi.HelpPage" version="5.2.4" targetFramework="net472" />
  <package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.7" targetFramework="net472" />
  <package id="Microsoft.AspNet.WebPages" version="3.2.7" targetFramework="net472" />
  <package id="Microsoft.AspNetCore.WebUtilities" version="2.0.2" targetFramework="net472" />
  <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="2.0.0" targetFramework="net472" />
  <package id="Microsoft.Extensions.Logging" version="2.0.2" targetFramework="net472" />
  <package id="Microsoft.Extensions.Logging.Abstractions" version="2.0.2" targetFramework="net472" />
  <package id="Microsoft.Extensions.Options" version="2.0.2" targetFramework="net472" />
  <package id="Microsoft.Extensions.Primitives" version="2.0.0" targetFramework="net472" />
  <package id="Microsoft.IdentityModel.JsonWebTokens" version="5.5.0" targetFramework="net472" />
  <package id="Microsoft.IdentityModel.Logging" version="5.5.0" targetFramework="net472" />
  <package id="Microsoft.IdentityModel.Protocols" version="5.5.0" targetFramework="net472" />
  <package id="Microsoft.IdentityModel.Protocols.OpenIdConnect" version="5.5.0" targetFramework="net472" />
  <package id="Microsoft.IdentityModel.Protocols.WsFederation" version="5.5.0" targetFramework="net472" />
  <package id="Microsoft.IdentityModel.Tokens" version="5.5.0" targetFramework="net472" />
  <package id="Microsoft.IdentityModel.Tokens.Saml" version="5.5.0" targetFramework="net472" />
  <package id="Microsoft.IdentityModel.Xml" version="5.5.0" targetFramework="net472" />
  <package id="Microsoft.Net.Http.Headers" version="2.0.2" targetFramework="net472" />
  <package id="Microsoft.Owin" version="4.0.1" targetFramework="net472" />
  <package id="Microsoft.Owin.Host.SystemWeb" version="4.0.1" targetFramework="net472" />
  <package id="Microsoft.Owin.Security" version="4.0.1" targetFramework="net472" />
  <package id="Microsoft.Owin.Security.ActiveDirectory" version="4.0.1" targetFramework="net472" />
  <package id="Microsoft.Owin.Security.Jwt" version="4.0.1" targetFramework="net472" />
  <package id="Microsoft.Owin.Security.OAuth" version="4.0.1" targetFramework="net472" />
  <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net472" />
  <package id="Newtonsoft.Json" version="10.0.3" targetFramework="net472" />
  <package id="Owin" version="1.0" targetFramework="net472" />
  <package id="PuppeteerSharp" version="1.12.0" targetFramework="net472" />
  <package id="PuppeteerSharp.AspNetFramework" version="1.12.0" targetFramework="net472" />
  <package id="System.Buffers" version="4.4.0" targetFramework="net472" />
  <package id="System.IdentityModel.Tokens.Jwt" version="5.5.0" targetFramework="net472" />
  <package id="System.IO" version="4.3.0" targetFramework="net472" />
  <package id="System.Net.Http" version="4.3.3" targetFramework="net472" />
  <package id="System.Numerics.Vectors" version="4.4.0" targetFramework="net472" />
  <package id="System.Runtime" version="4.3.0" targetFramework="net472" />
  <package id="System.Runtime.CompilerServices.Unsafe" version="4.5.0" targetFramework="net472" />
  <package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net472" />
  <package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net472" />
  <package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net472" />
  <package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net472" />
  <package id="System.Text.Encodings.Web" version="4.4.0" targetFramework="net472" />
  <package id="System.Threading.Tasks.Extensions" version="4.5.1" targetFramework="net472" />
</packages>
Jeremy Bond
  • 159
  • 1
  • 15
  • Do you have any unmanaged or unsafe code? – Timur Umerov Jun 16 '21 at 19:57
  • Not to my knowledge, but I'm not super clear on what "managed" vs "unmanaged" really is. I added the contents of the packages.config above. – Jeremy Bond Jun 16 '21 at 20:30
  • Can you provide stacktrace of the exception? – Timur Umerov Jun 16 '21 at 20:32
  • Your project references `System.Runtime.CompilerServices.Unsafe`, so you probably do have some unsafe code in your project. You should look for usages of `Unsafe` static class – Timur Umerov Jun 16 '21 at 20:39
  • Searching the entire project for "Unsafe" return 3 results. A reference in - MERP.dll.config – Jeremy Bond Jun 16 '21 at 20:53
  • Try removing `System.Runtime.CompilerServices.Unsafe` from your packages and then compiling the project (if there are build errors, please provide code segments, where the error occurs, please), – Timur Umerov Jun 16 '21 at 21:02
  • Attempting to gather dependency information for package 'System.Runtime.CompilerServices.Unsafe.4.5.0' with respect to project 'MERPBackend', targeting '.NETFramework,Version=v4.7.2' Resolving actions to uninstall package 'System.Runtime.CompilerServices.Unsafe.4.5.0' Unable to uninstall 'System.Runtime.CompilerServices.Unsafe.4.5.0' because 'Microsoft.Extensions.Primitives.2.0.0, System.Threading.Tasks.Extensions.4.5.1' depend on it. Time Elapsed: 00:00:00.6557026 ========== Finished ========== – Jeremy Bond Jun 16 '21 at 21:52
  • Seems like `System.Runtime.CompilerServices.Unsafe` is default for every project. I didn't know that. Have you checked this question yet [link](https://stackoverflow.com/questions/4074585/attempted-to-read-or-write-protected-memory-this-is-often-an-indication-that-ot) – Timur Umerov Jun 16 '21 at 22:08
  • I added a break point to the auth pipeline and started stepping through code. There's an FK Constraint between the Customer table and CompanyAssociation table. This is causing an infinite loop to occur in the Auto Generate Customer.tt and CompanyAssocation.tt because they both reference one another. – Jeremy Bond Jun 16 '21 at 22:21

2 Answers2

2

A couple things to eliminate:

The DbContext is not being disposed in calls like this.

MARQERPEntities ent = new MARQERPEntities();
var cst = ent.Customers.FirstOrDefault(w => w.ID == _input.ID);
return cst;

Should be:

using (MARQERPEntities ent = new MARQERPEntities())
{
    var cst = ent.Customers.FirstOrDefault(w => w.ID == _input.ID);
    return cst;
}

Next, Depending on what this MERP.Customer looks like in terms of properties and relationships via navigation properties I would strongly recommend declaring a DTO for the API to return rather than sending an entity. EF builds proxies for entities to facilitate things like lazy loading which serializers will pick up and transform into the concrete classes. When the serializers touch a virtual member they will trigger a lazy-load which is both a performance issue and can lead to exceptions. (Such as cyclic references) These exceptions may be masked by the Web API execution exception.

Look at declaring a POCO C# object for a DTO containing the Customer fields your consumers will be expecting and populate this with either Select or Automapper's ProjectTo rather than sending the Customer.

public CustomerDTO GetCustomerProfile([FromBody] Models.Generic.GuidValue _input)

    using (MARQERPEntities ent = new MARQERPEntities())
    {
        var cst = ent.Customers
            .Where(w => w.ID == _input.ID);
            .ProjectTo<CustomerDTO>(config)
            .SingleOrDefault();
        return cst;
    }
}

Where config is a MapperConfiguration instance initialized with any details about mapping the Customer to CustomerDTO (and any related entity to DTO mapping)

If you must send an entire Customer Graph then ensure all required navigation references are eager-loaded /w Include combined temporarily turning off lazy loading:

using (MARQERPEntities ent = new MARQERPEntities())
{
    ent.Configuration.LazyLoadingEnabled = false;
    var cst = ent.Customers
        .Include(w => w.Address) // as an example, Eager fetch anything you want included.
        .FirstOrDefault(w => w.ID == _input.ID);
    return cst;
}
Steve Py
  • 26,149
  • 3
  • 25
  • 43
  • The *Using* statement unwrapped the actual error and disabling LazyLoading solved the cyclical reference. We use POCO Objects in some places but avoid them when the Graph will suffice or should I say, _use to...._ I need to investigate a few things.
    _Are Proxies and POCOs similar in that they prevent Lazy Loading? A follow up to that, are Proxies created automatically by EF or is the Proxy the Graph Object?_
    _.Include only supports literal strings. How do you get LAMBDA support?_
    _I need to get a better understanding on how to use .ProjectTo_
    Mind blown, Thanks!
    – Jeremy Bond Jun 17 '21 at 17:46
  • The Lambda variant is part of Microsoft.EntifyFrameworkCore.QueryableExtensions, although it should be available /w `using Microsoft.EntifyFrameworkCore;` That is with EF Core 3.1, it may have moved in later versions or not available in earlier ones? I vaguely remember at one point needing to add a `using` reference or adding a Nuget package to get the Lambda include instead of just the string version, but I tried it in my test project and it seemed happy with just the EF reference. – Steve Py Jun 17 '21 at 20:51
0

When this first happened I wasn't sure how or when the behavior started or what the cause was. The EDMX is generated from the database, some times the EDMX gets corrupt and the easiest fix is to blow away the EDMX completely and reload it. When this happens the Lazy Loading Enabled setting reverts to true.

enter image description here

You have to open the EDMX in to a tab and go to Properties, you'll be presented with the ConceptualEntityModel properties. If you go to Solution Explorer select the EDMX and go to Properties you get the File Properties for the EDMX.

Jeremy Bond
  • 159
  • 1
  • 15