mamift / LinqToXsdCore

LinqToXsd ported to .NET Core (targets .NET Standard 2 for generated code and .NET Core 3.1, .NET 5+ for the code generator CLI tool).
Microsoft Public License
44 stars 15 forks source link

Alternate Enum Generation strategy #15

Open justinoverton opened 4 years ago

justinoverton commented 4 years ago

After the enum generation is completed I would be willing to make this change.

I have to work with XML files provided by vendors that define the enumeration restriction for fields that change somewhat often. This issue is what lead me to find LinqToXsdCore. When an xml enumeration is compiled into a .net enum, it cannot handle XML that comes across with a different value than specified in the schema.

Semantically, the schema says it is invalid. Treating enums as strings solves this, but the reality is that the generated enums are really helpful when producing input to a vendor. They may be useful when consuming a document, but also may never be looked at again.

I like LinqToXsdCore because I can load a document, look at only the sections I need, and save it. If the vendor added entirely new elements without telling me, it works fine in the round-trip.

I can't be the only one that's had this kind of issue with the strict enums and vendor controlled formats. I thought it might be worth while to look into a flexible enumeration strategy instead of compiled enums.

See: https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/enumeration-classes-over-enum-types

This defines a Enumeration that is integral with a name (just like regular enums), but for many XML restrictions it probably doesn't even need the integral part.

Code snippets from the above link:

public abstract class Enumeration : IComparable
{
    public string Name { get; private set; }

    public int Id { get; private set; }

    protected Enumeration(int id, string name)
    {
        Id = id;
        Name = name;
    }

    public override string ToString() => Name;

    public static IEnumerable<T> GetAll<T>() where T : Enumeration
    {
        var fields = typeof(T).GetFields(BindingFlags.Public |
                                         BindingFlags.Static |
                                         BindingFlags.DeclaredOnly);

        return fields.Select(f => f.GetValue(null)).Cast<T>();
    }

    public override bool Equals(object obj)
    {
        var otherValue = obj as Enumeration;

        if (otherValue == null)
            return false;

        var typeMatches = GetType().Equals(obj.GetType());
        var valueMatches = Id.Equals(otherValue.Id);

        return typeMatches && valueMatches;
    }

    public int CompareTo(object other) => Id.CompareTo(((Enumeration)other).Id);

    // Other utility methods ...
}

public class CardType : Enumeration
{
    public static readonly CardType Amex = new CardType(1, "Amex");
    public static readonly CardType Visa = new CardType(2, "Visa");
    public static readonly CardType MasterCard = new CardType(3, "MasterCard");

    public CardType(int id, string name)
        : base(id, name)
    {
    }
}

I was thinking about just post-processing the code dom. But after looking into the get/set accessors I decided if I can get this feature to be properly supported in this library that would be much easier.

mamift commented 4 years ago

Sounds like a neat idea; but if you do decide to implement this I strongly recommend that you implement it via post-processing the generated code, so use Roslyn transformations on the return TextWriter instance given by the Generate(XmlSchemaSet schemaSet, LinqToXsdSettings settings) method in the XObjectsCoreGenerator class.

The existing code base for generating code from an XSD is so very convoluted and written in a now 13-year old style that it's hard to bug fix and re-factor. Fixing that enum issue for instance took quite a while.