Breeze / breeze.server.net

Breeze support for .NET servers
MIT License
76 stars 62 forks source link

enums datatype is now Int32 after migrating to .net 8 #196

Closed graphicsxp closed 2 months ago

graphicsxp commented 3 months ago

Hello,

we have noticed that after migrating to .net 8, all enums datatype are now int32. For instance in generated metadata: { "dataType": "MyNamespace.AccessRequest+AccessRequestStatus", "enumType": "AccessRequestStatus:#Op", "isNullable": false, "nameOnServer": "Status", "validators": [ { "name": "required" } ] }

is now

{ "dataType": "Int32", "enumType": "AccessRequestStatus:#MyNamespace", "isNullable": false, "nameOnServer": "Status", "validators": [ { "name": "required" } ] }

what is the reason for that ?

now we are getting tons of errors in our code Type 'string' is not assignable to type 'number'. because we are doing compare on strings, not on number.

steveschmitt commented 3 months ago

It was changed because Enum behavior is more cleanly represented by int on both the client and server side of things.

That said, this is a breaking change that should have been enabled with a feature flag. Mea culpa.

If you are generating TypeScript entity classes using Breeze Entity Generator, the latest versions handle the numeric enums correctly.

graphicsxp commented 3 months ago

ok, thanks for the feedback. out of interest, does this mean you redefine enums on the client side ? and you access them like myEnum.SomeValue, instead of by string 'SomeValue' as we do now ?

steveschmitt commented 3 months ago

Right. Suppose in my C# code I have a User entity that has a property of type GenderEnum:

public class User 
{
    public Guid Id { get; set; }
    public GenderEnum GenderId { get; set; }
...
}

public enum GenderEnum
{
    NotSpecified,
    Male, 
    Female
}

Then my Breeze metadata would have a User entity with a property of type GenderEnum. GenderEnum would be defined in the enumTypes section :

{
  "structuralTypes": [
...
    {
      "shortName": "User",
      "namespace": "My.Models",
...
      "dataProperties": [
...
        {
          "dataType": "Int32",
          "enumType": "GenderEnum:#My.Models",
          "isNullable": false,
          "nameOnServer": "GenderId",
          "validators": [
            {
              "name": "required"
            }
          ]
        },
...
      ]
    }
  ],
  "enumTypes": [
    {
      "shortName": "GenderEnum",
      "namespace": "My.Models",
      "values": [
        "NotSpecified",
        "Male",
        "Female"
      ],
      "ordinals": [
        0,
        1,
        2
      ]
    },
 ]

Then if I use Breeze Entity Generator to generate TypeScript classes, it will create a User class with a genderId property of type GenderEnum:

export class User extends BaseEntity  {
  /** Guid key */
  id!: string;
  /** Enum FK gender */
  genderId!: GenderEnum;
}

And also an enums.ts file with the TypeScript enum definitions:

export enum GenderEnum {
  NotSpecified = 0,
  Male = 1,
  Female = 2,
}

So I can use GenderEnum in my TypeScript code like I would in C#:

if (user.genderId == GenderEnum.Male) { ... }
graphicsxp commented 3 months ago

oh right, very nice.

I need to upgrade the generator then :)

thanks

graphicsxp commented 2 months ago

I have updated my code to use Int32 enums. I can see in my metadata that my enums properties are correctly defined, e.g: { "dataType": "Int32", "enumType": "MaterialType:#Op", "isNullable": false, "nameOnServer": "MaterialType", "validators": [ { "name": "required" } ] },

enums were generated just fine :

export enum MaterialType { Reference = 0, Manuscript = 1, }

Yet, when I query my data, it returns as a string instead of an Int32 : image

Any idea why this is returned as a string ?

Obviously it breaks the code because if I do that :

myEntity.materialType === MaterialType.Reference returns false

graphicsxp commented 2 months ago

I think it's because of this line in the breeze jSONSerializationFns.cs file :

´ss.Converters.Add(new StringEnumConverter());´

Why does breeze still return enums as string, since now the client handles proper enums ?

Or should the breeze client be able to handle the string enums ?

steveschmitt commented 2 months ago

I think the client should be able to handle string enums, but numbers are still preferred. We should change the UpdateWithDefaults to reflect that. In the meantime, here's how we have JSON configured in one of our projects:

        var mvcBuilder = services.AddMvc();

        mvcBuilder.AddNewtonsoftJson(opt => {
            var ss = JsonSerializationFns.UpdateWithDefaults(opt.SerializerSettings);
            if (ss.ContractResolver is DefaultContractResolver resolver) {
                resolver.NamingStrategy = null;  // remove json camelCasing; names are converted on the client.
            }

            // UpdateWithDefaults installs StringEnumConverter.  Remove it for numeric enums.
            var enumConverter = ss.Converters.FirstOrDefault(c => c is Newtonsoft.Json.Converters.StringEnumConverter);
            if (enumConverter != null) {
                ss.Converters.Remove(enumConverter);
            }
        });
graphicsxp commented 2 months ago

no, my client does not seem to handle the enums correctly if sent as string. Is there some kind of configuration settings on the client regarding enums ? I think for you it works because you are explicitly removing the stringenum converter. But why do you keep it in breeze core code then ????

graphicsxp commented 2 months ago

ok, just to confirm that if remove the converter like you do, my enums are working just fine indeed

so maybe it should be removed directly from breeze code, no ?

steveschmitt commented 2 months ago

Yes, I agree that it should be removed.

steveschmitt commented 2 months ago

As of release 7.3.0, enums can be either string or int. The default is string, for backward compatibility and least surprise. If you want to use int enums, put the following in your startup code:

    BreezeConfig.Instance.UseIntEnums = true;
    mvcBuilder.AddNewtonsoftJson(opt => {
        var ss = JsonSerializationFns.UpdateWithDefaults(opt.SerializerSettings, false, BreezeConfig.Instance.UseIntEnums);
    });

Note that you no longer need to set the NamingStrategy or remove the StringEnumConverter; UpdateWithDefaults() does that for you, based on its parameters.