Search code examples
c#entity-frameworkasp.net-web-apiodatabreeze

Breeze EdmBuilder custom enum serialization error


I am trying to get breeze to work with my webapi in combination with entity framework but seem to be stuck when i try to query a certain entity that is using a custom enum.

I am using the breeze EDMbuilder to generate the edm model for my metadata.

My config:

config.Routes.MapODataServiceRoute(
    routeName: "odata",
    routePrefix: "odata",
    model: EdmBuilder.GetEdm<Base.DAL.Entities.DbContextFixForEdm>(),
    batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer)
);

The metadata is generated and if i query odata/$metadata i see all of my entities with their properties.

Now the problem i am facing is as follows.

I have a very basic entity called ApiUserEntity with the following properties:

public class ApiUserEntity : BaseEntity
{
    public string Username { get; set; }
    public string Password { get; set; }
    public string Email { get; set; }
    public string Salt { get; set; }

    public ApiUserRole Role { get; set; }

    public ApiPermission Permission { get; set; }
}

And a simple Odatacontroller get function which returns an iqueryable of ApiUserEntities:

// GET: odata/ApiUsers
[EnableQuery]
public IQueryable<ApiUserEntity> GetApiUsers(ODataQueryOptions<ApiUserEntity> opts)
{
    return _userService.GetUsers();
}

However whenever i query this method i always get back the following error:

'Base.DAL.Entities.ApiUserRole' cannot be serialized using the ODataMediaTypeFormatter.

This error is not just when i query it with breeze but also when i acces the method from my browser.

In the metadata file generated the apiuserentity looks like this:

<EntityType xmlns:p5="http://schemas.microsoft.com/ado/2013/11/edm/customannotation" Name="ApiUserEntity" p5:ClrType="Base.DAL.Entities.ApiUserEntity, Base.DAL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
    <Key>
    <PropertyRef Name="Id"/>
    </Key>
    <Property xmlns:p7="http://schemas.microsoft.com/ado/2009/02/edm/annotation" Name="Id" Type="Edm.Int32" Nullable="false" p7:StoreGeneratedPattern="Identity"/>
    <Property Name="Username" Type="Edm.String" Nullable="false" MaxLength="255" FixedLength="false" Unicode="true"/>
    <Property Name="Password" Type="Edm.String" Nullable="false" MaxLength="300" FixedLength="false" Unicode="true"/>
    <Property Name="Email" Type="Edm.String" Nullable="false" MaxLength="255" FixedLength="false" Unicode="true"/>
    <Property Name="Salt" Type="Edm.String" MaxLength="255" FixedLength="false" Unicode="true"/>
    <Property Name="Role" Type="Base.DAL.Entities.ApiUserRole" Nullable="false"/>
    <Property Name="Permission" Type="Base.DAL.Entities.ApiPermission" Nullable="false"/>
    <Property Name="CreatedAt" Type="Edm.DateTime"/>
    <NavigationProperty Name="Domains" Relationship="Base.DAL.Entities.DomainEntity_Users" ToRole="DomainEntity_Users_Source" FromRole="DomainEntity_Users_Target"/>
</EntityType>

The main thing i noticed is that it is adding an Edm prefix to common types like strings and datetime. But my custom enums are just their full namespace. When i change the properties of custom enums to int it will give me back results but i would really want to use these enums and turning them into ints would not be a solution.

I am geussing it cant find the type and doesnt know how to parse it but that is just geussing. Other than that i have no idea how to solve it or where i should go from here. Have been banging my head over this for the last couple of hours without result.


Solution

  • I use the following classes to build the Edm Model:

    public class ApiUserEntity // : BaseEntity
    {
            public int Id { get; set; }
            public string Username { get; set; }
            public string Password { get; set; }
            public string Email { get; set; }
            public string Salt { get; set; }
    
            public ApiUserRole Role { get; set; }
    
            public ApiPermission Permission { get; set; }
    }
    
    public enum ApiUserRole
    {
            Admin,
            Guest
    }
    
    public enum ApiPermission
    {
            Write,
            Read,
            WriteRead
    }
    

    Here's the metadata document:

    <?xml version="1.0" encoding="utf-8"?>
    <edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
      <edmx:DataServices>
        <Schema Namespace="WebApplication1.Models" xmlns="http://docs.oasis-open.org/odata/ns/edm">
          <EntityType Name="ApiUserEntity">
            <Key>
              <PropertyRef Name="Id" />
            </Key>
            <Property Name="Id" Type="Edm.Int32" Nullable="false" />
            <Property Name="Username" Type="Edm.String" />
            <Property Name="Password" Type="Edm.String" />
            <Property Name="Email" Type="Edm.String" />
            <Property Name="Salt" Type="Edm.String" />
            <Property Name="Role" Type="WebApplication1.Models.ApiUserRole" Nullable="false" />
            <Property Name="Permission" Type="WebApplication1.Models.ApiPermission" Nullable="false" />
          </EntityType>
          <EnumType Name="ApiUserRole">
            <Member Name="Admin" Value="0" />
            <Member Name="Guest" Value="1" />
          </EnumType>
          <EnumType Name="ApiPermission">
            <Member Name="Write" Value="0" />
            <Member Name="Read" Value="1" />
            <Member Name="WriteRead" Value="2" />
          </EnumType>
        </Schema>
        <Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">
          <EntityContainer Name="Container">
            <EntitySet Name="ApiUserEntitys" EntityType="WebApplication1.Models.ApiUserEntity" />
          </EntityContainer>
        </Schema>
      </edmx:DataServices>
    </edmx:Edmx>
    

    Custom enum types with their full namespace is correct. I think your problem is your method definition. Please try to modify your method as:

    [EnableQuery]
    public IQueryable<ApiUserEntity> GetApiUsers()
    {
        return _userService.GetUsers();
    }
    

    That is, don't writeEnableQueryAttribute and ODataQueryOptions<ApiUserEntity> opts for a method at the same time.

    After modification, here's my sample test:

    GET ~/odata/ApiUserEntitys

       {
      "@odata.context":"http://localhost:40502/odata/$metadata#ApiUserEntitys","value":[
        {
          "Id":1,"Username":"UserName #1","Password":"Password #1","Email":"Email #1","Salt":"Salt E1","Role":"Admin","Permission":"WriteRead"
        },{
          "Id":2,"Username":"UserName #2","Password":"Password #2","Email":"Email #2","Salt":"Salt E2","Role":"Admin","Permission":"WriteRead"
        },{
          "Id":3,"Username":"UserName #3","Password":"Password #3","Email":"Email #3","Salt":"Salt E3","Role":"Admin","Permission":"WriteRead"
        },{
          "Id":4,"Username":"UserName #4","Password":"Password #4","Email":"Email #4","Salt":"Salt E4","Role":"Admin","Permission":"WriteRead"
        },{
          "Id":5,"Username":"UserName #5","Password":"Password #5","Email":"Email #5","Salt":"Salt E5","Role":"Admin","Permission":"WriteRead"
        }
      ]
    }
    

    Hope it can help you. Thanks.