简体   繁体   中英

EntityFramework extract to CSV but keeping Data Annotations

I am trying to export a quite complex model (in terms of hierarchy) to a CSV file. I am trying various approaches but all of them have something missing or require a lot of coding which I would like to avoid.

What I would like to have as a result is the CSV extract of the values, with the CSV headers coming from the DisplayName attribute annotations I used on the EF models themselves.

I have tried CSVHelper, but the automapper feature is a bit messy and it's not possible anymore to Ignore certain fields with attributes; besides it does not support naming from the EF DisplayAttribute .

I have also tried to map the model to a separate CSV model using AutoMapper, then using CsvHelper to dump this model but again I end up writing a lot of code to set the column names.

Do you know of any quick solution to get what I need? I could possibly go into CsvHelper source code and rewrite the part that takes the property name but I don't know how complex the codebase would be. Does anyone have any idea?

Any insight is highly appreciated.

Attributes were removed because they are very limiting in their functionality. The mapping structure allows much more possibilities.

You should be able to map directly to your EF models. This is another reason why mapping is better. It allows you to map to classes that you can't change the source to, unlike attributes.

If the class matches your CSV file, you don't need to do anything.

void Main()
{
    using( var stream = new MemoryStream() )
    using( var reader = new StreamReader( stream ) )
    using( var writer = new StreamWriter( stream ) )
    using( var csv = new CsvWriter( writer ) )
    {
        var people = new List<Person>
        {
            new Person
            {
                Name = "Joe User",
                Address = new Address
                {
                    Street = "123 4th Street",
                },
            },
        };

        csv.WriteRecords( people );
        writer.Flush();
        stream.Position = 0;

        reader.ReadToEnd().Dump();
    }
}

public class Person
{
    public string Name { get; set; }

    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }
}

Output:

Name,Street
Joe User,123 4th Street

If you want to make changes, you can create a mapping file to explain those changes. You can still AutoMap and just change the things you want.

void Main()
{
    using( var stream = new MemoryStream() )
    using( var reader = new StreamReader( stream ) )
    using( var writer = new StreamWriter( stream ) )
    using( var csv = new CsvWriter( writer ) )
    {
        var people = new List<Person>
        {
            new Person
            {
                Name = "Joe User",
                Address = new Address
                {
                    Street = "123 4th Street",
                },
            },
        };

        csv.Configuration.RegisterClassMap<PersonMap>();
        csv.WriteRecords( people );
        writer.Flush();
        stream.Position = 0;

        reader.ReadToEnd().Dump();
    }
}

public class Person
{
    public string Name { get; set; }

    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }
}

public sealed class PersonMap : CsvClassMap<Person>
{
    public PersonMap()
    {
        AutoMap();
        Map( m => m.Name ).Name( "Full Name" );
    }
}

Output:

Full Name,Street
Joe User,123 4th Street

You can manage the references and have full control too if you want.

I honestly don't think attributes will ever make it back in. There are people that REALLY want to use them though. An option I've been thinking of is to make an extensions add on where some of these other things that I don't think should be a part of the main library, could still exist for those who want to use it. Or better yet, someone else should start a CsvHelperContrib project and include things like that. Attribute mapping and DataAnnotations could be a part of it.

In the end I went for the CsvHelper source code and added some code in there. First of all I have created two attributes for Ignoring and specifying a name (I don't know why the author decided to remove the attributes in v2):

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class CsvIgnoreAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class CsvHeaderAttribute : Attribute
{
    /// <summary>
    /// The name to use for the CSV column header
    /// </summary>
    public string Name { get; set; }
}

Then I modified the CsvPropertyMap constructor to take into account the attributes from the property, starting from the specific CsvHeader attribute then falling back to the DisplayAttribute then to the property name. Potentially it could be extended to take into account also the DisplayNameAttribute and eventually use something like Humanizer as a fallback.

This is the amended constructor; notice how the CsvIgnore is actually taken also from the base property if present. This helps in case of inheritance.

public CsvPropertyMap( PropertyInfo property )
{
    data = new CsvPropertyMapData( property )
    {
        // Set some defaults.
        TypeConverter = TypeConverterFactory.GetConverter( property.PropertyType )
    };

    var displayAttributes = property.GetCustomAttributes(typeof(CsvHeaderAttribute), inherit: false);
    if (displayAttributes.Any()) {
        var displayName = ((CsvHeaderAttribute)displayAttributes.First()).Name;
        data.Names.Add(displayName);
    }
    else {
        displayAttributes = property.GetCustomAttributes(typeof (DisplayAttribute), inherit: false);
        if (displayAttributes.Any()) {
            var displayName = ((DisplayAttribute) displayAttributes.First()).Name;
            data.Names.Add(displayName);
        }
        else {
            data.Names.Add(property.Name);
        }
    }

    var shouldIgnore = Attribute.GetCustomAttribute(property, typeof (CsvIgnoreAttribute), inherit: true);
    if (shouldIgnore != null)
        data.Ignore = true;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM