this.Map(x => x.Foo.Bar).Name("bar");
Property 'System.String bar' is not defined for type ...
I think this should work when I explicitly stated Name.
You cannot use nested property accessors like that...you'll need to use a
reference map. Check the docs.
On May 18, 2016 7:39 AM, "Dzmitry Lahoda" [email protected] wrote:
this.Map(x => x.Foo.Bar).Name("bar");
Property 'System.String bar' is not defined for type ...
I think this should work when I explicitly stated Name.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub
https://github.com/JoshClose/CsvHelper/issues/526
Yes. I working on changing my code. So there still could be usability change for error - detected this case and ex with message 'for nested foo.bar use reference map'
Also it still could be useful if I set Name directly then I should be able to use nested.
Reference will not work for me. As I want to have some properties from first reference, then some simple maps, then some from first reference, then some from second, then some from maps again, then from second reference. Event if it fixed with splitting into Index calls into several maps then it still bad - I want declarative conversion of stuff in single places without errors. Having Index for one CSV in difference files is error prone. So I have to use manual record writing or duplicating work for creating more code for classes and properties.
Can you provide an example of what you need? My initial thought is you should just loop the records manually, but we can see based on an example.
Here is pseudo code (real code in commercial app, please note if something is not clear here):
class A (string P1, string P2);
class B (string P3, string P4);
class C (A Ref1, B Ref2, string P5);
I want next in CSV map
Ref1.P1, "renamedP1"
Ref2.P3, "p3"
Ref1.P2, "p2"
P5, "p5"
Ref2.P4, "renamedP4"
I having 21 properties(CSV columns) in real and yes have 2 options - flat out all them into C or loop records manually. Question is : how to do what I want with less code and as declarative as possible to be safe?
I would be glad to flat these out into C class in Scala/F#/C#7 where creating properties and delegation is much of less keystrokes. Things get bad because our codebase under idiotic rules of StyleCop - so each property should have proper comment:(.
When looping manually I do not see directly property - name - index relation, so may have some mistakes.
I'm still not following exactly. Can you create sample code that shows it? So, don't use the commercial proprietary code, but create a sample the demonstrates the same thing.
Please reopen:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace csv
{
class Program
{
public class A
{
public B PropA { get; set; }
}
public class B
{
public C PropB { get; set; }
}
public class C
{
public string Val { get; set; }
}
public class MyMap : CsvHelper.Configuration.CsvClassMap<A>
{
public MyMap()
{
this.Map(x => x.PropA.PropB.Val).Name("BlaBla");
}
}
static void Main(string[] args)
{
Console.WriteLine(Environment.Version);
Console.WriteLine(typeof(CsvHelper.CsvFactory).Assembly.FullName);
var conf = new CsvHelper.Configuration.CsvConfiguration();
conf.RegisterClassMap<MyMap>();
var wr = new StringWriter();
var csv = new CsvHelper.CsvWriter(wr, conf);
// NEXT WORKS WHICH IS WRONG - BOTH SHOULD FAIL OF BOTH SHOULD WORK!!!!
//var csv = new CsvHelper.CsvWriter(wr);
csv.WriteHeader<A>();
csv.WriteRecord(new A() { PropA = new B { PropB = new C { Val = "c" } } });
wr.Flush();
Console.WriteLine(wr);
}
}
}
Gives
4.0.30319.42000
CsvHelper, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8c4959082be5c823
Unhandled Exception: System.ArgumentException: Property 'System.String Val' is not defined for type 'csv.Program+A'
at System.Linq.Expressions.Expression.Property(Expression expression, PropertyInfo property)
at CsvHelper.CsvWriter.CreatePropertyExpression(Expression recordExpression, CsvClassMap mapping, CsvPropertyMap propertyMap) in C:\Users\Josh\Projects\CsvHelper\src\CsvHelper\CsvWriter.cs:line 568
at CsvHelper.CsvWriter.CreateActionForObject(Type type) in C:\Users\Josh\Projects\CsvHelper\src\CsvHelper\CsvWriter.cs:line 747
at CsvHelper.CsvWriter.CreateWriteRecordAction(Type type) in C:\Users\Josh\Projects\CsvHelper\src\CsvHelper\CsvWriter.cs:line 710
at CsvHelper.CsvWriter.GetWriteRecordAction[T]() in C:\Users\Josh\Projects\CsvHelper\src\CsvHelper\CsvWriter.cs:line 668
at CsvHelper.CsvWriter.WriteRecord[T](T record) in C:\Users\Josh\Projects\CsvHelper\src\CsvHelper\CsvWriter.cs:line 399
at csv.Program.Main(String[] args) in G:\src\clr\data\csv\csv\Program.cs:line 46
But without config:
var csv = new CsvHelper.CsvWriter(wr);
I am fine
4.0.30319.42000
CsvHelper, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8c4959082be5c823
Val
c
I expect that both things to work - with and without config. If first version with config does not work and your insist it should not - then without config should not work either. If first version does not work it should throw other exception - suggesting what to do - like flat out model or use references.
But as I said - references DO NOT ALLOW INTERMINGLE COLUMNS AS I WANT!
Here is intermingle. Is it possible to do same with references? May be by putting numbers into different maps tying these? But I will repeat - MAPs are valuable if these allow flat out and as it goes numbering in one place for declarative CSV description. If I need to flat in one class, put 1 5 in on map, 2 and 4 in other - I very probable to mistake.
public class A
{
public B PropA { get; set; }
public string P1 { get; set; }
public string P2 { get; set; }
}
public class B
{
public C PropB { get; set; }
public string P3 { get; set; }
public string P4 { get; set; }
}
public class C
{
public string Val { get; set; }
public string P3 { get; set; }
public string P4 { get; set; }
}
public class MyMap : CsvHelper.Configuration.CsvClassMap<A>
{
public MyMap()
{
this.Map(x => x.PropA.PropB.Val).Name("BlaBla");
this.Map(x => x.PropA.P3).Name("P3");
this.Map(x => x.P1).Name("P1");
this.Map(x => x.PropA.PropB.P4).Name("P4");
this.Map(x => x.P2).Name("P2");
}
}
Outputting without using the map, I get this:
Val,P3,P4,P3,P4,P1,P2
val,P5,P6,P3,P4,P1,P2
Using the map fails as you have stated.
Based on your mapping, I created a mapping structure using references that I think is the same thing. This is the output.
BlaBlah,P3,P1,P4,P2
val,P3,P1,P6,P2
Here is the full example.
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 a = new A
{
P1 = "P1",
P2 = "P2",
PropA = new B
{
P3 = "P3",
P4 = "P4",
PropB = new C
{
P3 = "P5",
P4 = "P6",
Val = "val"
}
}
};
var records = new List<A> { a };
//csv.Configuration.RegisterClassMap<MyMap>();
csv.Configuration.RegisterClassMap<AMap>();
csv.WriteRecords( records );
writer.Flush();
stream.Position = 0;
reader.ReadToEnd().Dump();
}
}
public class A
{
public B PropA { get; set; }
public string P1 { get; set; }
public string P2 { get; set; }
}
public class B
{
public C PropB { get; set; }
public string P3 { get; set; }
public string P4 { get; set; }
}
public class C
{
public string Val { get; set; }
public string P3 { get; set; }
public string P4 { get; set; }
}
public class MyMap : CsvHelper.Configuration.CsvClassMap<A>
{
public MyMap()
{
this.Map(x => x.PropA.PropB.Val).Name("BlaBla");
this.Map(x => x.PropA.P3).Name("P3");
this.Map(x => x.P1).Name("P1");
this.Map(x => x.PropA.PropB.P4).Name("P4");
this.Map(x => x.P2).Name("P2");
}
}
public sealed class AMap : CsvClassMap<A>
{
public AMap()
{
Map( m => m.P1 ).Index( 2 ).Name( "P1" );
Map( m => m.P2 ).Index( 4 ).Name( "P2" );
References<BMap>( m => m.PropA );
}
}
public sealed class BMap : CsvClassMap<B>
{
public BMap()
{
Map( m => m.P3 ).Index( 1 ).Name( "P3" );
References<CMap>( m => m.PropB );
}
}
public sealed class CMap : CsvClassMap<C>
{
public CMap()
{
Map( m => m.P4 ).Index( 3 ).Name( "P4" );
Map( m => m.Val ).Index( 0 ).Name( "BlaBlah" );
}
}
Does reference mapping like this not work for you?
I can understand that looking at the the single map using sub properties is much nicer, though I don't know how real world the example is for you.
The reason it works if you don't specify a mapping is it uses auto mapping and goes through the class structure and creates reference maps automatically for you. I could do a similar thing if you pass a sub-property into a map. The possible issue with that is, if you did sub-property and also created a References map, the References map would overwrite the other one.
Let me know your thoughts.
Such reference map will work sure. But it is much better (especially) in C#6+ without build rules to add comments on each property just do like of next(pseudo code):
new AFlat(A x)
{
BlaBlah = x.PropA.PropB.Val,
P3 = x.PropA.P3,
P1 = x.P1,
P4 = x.PropA.PropB.P4,
P2 = x.P2
}
So no references, write some flat class with little code as possible. And CSV it. In my case we use C# 5 and build rules to have comments, spaces, empty lines as needed. So it is hard to write such simple class.
I could do a similar thing if you pass a sub-property into a map. - would be great.
The possible issue with that is, if you did sub-property and also created a References map, the References map would overwrite the other one. - may be throw conflict exception for this.
Overall uses case - given I have object with child properties (some of these are collections) and I have other object from other data source. I put them into tuple and then map into flat CSV report. I hope to streamline this process to create reports as easy and as declaratively as possible in code.
I need simple map in order to we correct in answers - where data is came from.
I've been working on this the past few nights and was able to get it working. I'll put it out on NuGet tonight some time.
Here is a passing test.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CsvHelper.Configuration;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CsvHelper.Tests.Mappings
{
[TestClass]
public class SubPropertyMappingTests
{
[TestMethod]
public void ReadTest()
{
using( var stream = new MemoryStream() )
using( var reader = new StreamReader( stream ) )
using( var writer = new StreamWriter( stream ) )
using( var csv = new CsvReader( reader ) )
{
writer.WriteLine( "P3,P1,P2" );
writer.WriteLine( "p3,p1,p2" );
writer.Flush();
stream.Position = 0;
csv.Configuration.RegisterClassMap<AMap>();
var records = csv.GetRecords<A>().ToList();
Assert.AreEqual( "p1", records[0].P1 );
Assert.AreEqual( "p2", records[0].B.P2 );
Assert.AreEqual( "p3", records[0].B.C.P3 );
}
}
[TestMethod]
public void WriteTest()
{
using( var stream = new MemoryStream() )
using( var reader = new StreamReader( stream ) )
using( var writer = new StreamWriter( stream ) )
using( var csv = new CsvWriter( writer ) )
{
var list = new List<A>()
{
new A
{
P1 = "p1",
B = new B
{
P2 = "p2",
C = new C
{
P3 = "p3"
}
}
}
};
csv.Configuration.RegisterClassMap<AMap>();
csv.WriteRecords( list );
writer.Flush();
stream.Position = 0;
var expected = "P3,P1,P2\r\n";
expected += "p3,p1,p2\r\n";
var result = reader.ReadToEnd();
Assert.AreEqual( expected, result );
}
}
private class A
{
public string P1 { get; set; }
public B B { get; set; }
}
private class B
{
public string P2 { get; set; }
public C C { get; set; }
}
private class C
{
public string P3 { get; set; }
}
private sealed class AMap : CsvClassMap<A>
{
public AMap()
{
Map( m => m.B.C.P3 ).Index( 0 );
Map( m => m.P1 ).Index( 1 );
Map( m => m.B.P2 ).Index( 2 );
}
}
}
}
cacf28e3ca602e1b4f6d65c6346478a088d8f9d5
This is available on NuGet as pre-release 3.0.0-beta3.