MetadataType buddy class for EF generated POCO - a

2019-07-22 06:53发布

问题:

I am using EF5 database first. In order to serialize a generated POCO class, I need to use the XMLIgnore attribute as follows

    public partial class Demographics
{
    public string KeyTract { get; set; }
    public string CollectionYear { get; set; }
    public string MSAFIPS { get; set; }
    ...
    ...
    ...
    [XmlIgnore()]
    public virtual ICollection<LoanApp> LoanApps { get; set; }
}

In order to keep from adding this each time I have EF re-create the model from the database, I added a "buddy class"

    [MetadataType(typeof(Demographic_Metadata))]
public partial class Demographics
{
}

public class Demographic_Metadata
{
    [XmlIgnore()]
    public virtual ICollection<LoanApp> LoanApps { get; set; }
}

But when I attempt to serialize the Demographics object using XmlSerializer I get "There was an error reflecting type ...Demographics".

In researching SO it appears that XMLSerializer ignores the buddy class. But has anyone found a workaround to avoid adding the XMLIgnore attribute each time the POCO class is regenerated?

回答1:

You can do this by using the XmlSerializer overload where you pass in the XmlAttributeOverrides. All we need to do is populate it via TypeDescriptor and make an ICustomAttributeProvider.

first the ICustomAttributeProvider is the simplest one, mainly because I'm just ignoring the inhert flag and always returning back all the attributes. The idea is that we will just pass in the attributes we want the XmlSerializer to know about.

public class CustomAttributeProvider : ICustomAttributeProvider
{
    private readonly object[] _attributes;

    public CustomAttributeProvider(params Attribute[] attributes)
    {
        _attributes = attributes;
    }

    public CustomAttributeProvider(AttributeCollection attributeCollection)
        : this(attributeCollection.OfType<Attribute>().ToArray())
    {
    }

    public object[] GetCustomAttributes(bool inherit)
    {
        return _attributes;
    }

    public object[] GetCustomAttributes(Type attributeType, bool inherit)
    {
        return _attributes.Where(attributeType.IsInstanceOfType).ToArray();
    }

    public bool IsDefined(Type attributeType, bool inherit)
    {
        return _attributes.Any(attributeType.IsInstanceOfType);
    }
}

Now I'm going to create a factory method to create the XMLAttributeOverrides.

We need to tell TypeDescriptor about the buddy class and that's what adding AssociatedMetadataTypeTypeDescriptionProvider to the provider list does.

From that we use TypeDescriptor to read the class attributes and the properties. Since buddy classes don't allow fields and TypeDescriptor doesn't read the fields either, so I use reflection for fields.

public static class BuddyClass
{
    public static XmlAttributeOverrides CreateXmlAttributeOverrides(Type type)
    {
        // Tell TypeDescriptor about the buddy class
        TypeDescriptor.AddProvider(new AssociatedMetadataTypeTypeDescriptionProvider(type), type);
        var xmlAttributeOverrides = new XmlAttributeOverrides();
        xmlAttributeOverrides.Add(type, new XmlAttributes(new CustomAttributeProvider(TypeDescriptor.GetAttributes(type))));

        foreach (PropertyDescriptor props in TypeDescriptor.GetProperties(type))
        {
            if (props.Attributes.Count > 0)
            {
             xmlAttributeOverrides.Add(type, props.Name, new XmlAttributes(new CustomAttributeProvider(props.Attributes)));
            }
        }

        foreach (var field in type.GetFields())
        {
            var attributes = field.GetCustomAttributes(true).OfType<Attribute>().ToArray();
            if (attributes.Any())
            {
                xmlAttributeOverrides.Add(type, field.Name, new XmlAttributes(new CustomAttributeProvider(attributes)));
            }
        }

        return xmlAttributeOverrides;
    }
}

You would call it like

var serializer = new XmlSerializer(typeof(Test), BuddyClass.CreateXmlAttributeOverrides(typeof(Test)));

Then user serializer like you normally would. This doesn't handle the attributes on parameters or return values.