Hey Leute,
ich habe gerade auf einen Post zum Thema CSV-Import geantwortet und gedacht, dass meine Antwort eventuell auch was für den Sourcecode-Austausch ist.
Der folgende Code soll die Übertragung von Daten aus einer CSV-Datei in ein klassenbasiertes Datenmodel erleichtern. Zunächst schreiben wir uns eine Basisklasse. Die kann nichts, muss aber existieren.
Anschließend schreiben wir uns zwei Attribute. Unter Umständen kann man hier auch auf bereits vorhandene Attribute zurückgreifen. Zur Vervollständigung nehme ich sie dennoch mit auf.
Order Attribut
ElementName Attribut
Jetzt kommt der eigentliche Part, der die Logik enthält: Serialize ist die Exportmethode, Deserialize die Import-Methode
Logic
Und anschließend legt ihr euch für jeden Importtyp eine Klasse an, die vom Basisdatenmodel erbt. Die Reihenfolge der Spalten in der CSV steuert ihr über das OrderAttribut, Spaltenüberschriften über das ElementName-Attribut.
Ein DataModel würde dann beispielsweise so aussehen:
Export zu CSV:
Export CSV
ich habe gerade auf einen Post zum Thema CSV-Import geantwortet und gedacht, dass meine Antwort eventuell auch was für den Sourcecode-Austausch ist.
Der folgende Code soll die Übertragung von Daten aus einer CSV-Datei in ein klassenbasiertes Datenmodel erleichtern. Zunächst schreiben wir uns eine Basisklasse. Die kann nichts, muss aber existieren.
Anschließend schreiben wir uns zwei Attribute. Unter Umständen kann man hier auch auf bereits vorhandene Attribute zurückgreifen. Zur Vervollständigung nehme ich sie dennoch mit auf.
VB.NET-Quellcode
- <AttributeUsage(AttributeTargets.Property, Inherited:=False, AllowMultiple:=False)>
- Public NotInheritable Class OrderAttribute
- Inherits Attribute
- Private ReadOnly _Order As Integer
- Public Sub New(ByVal Optional OrderNumber As Integer = 0)
- _Order = OrderNumber
- End Sub
- Public ReadOnly Property Order As Integer
- Get
- Return _Order
- End Get
- End Property
- End Class
VB.NET-Quellcode
- <AttributeUsage(AttributeTargets.Property, Inherited:=False, AllowMultiple:=False)>
- Public NotInheritable Class ElementNameAttribute
- Inherits Attribute
- Private ReadOnly _ElementName As String
- Public Sub New(ByVal Optional Name As String = "")
- _ElementName = Name
- End Sub
- Public ReadOnly Property ElementName As String
- Get
- Return _ElementName
- End Get
- End Property
- End Class
Jetzt kommt der eigentliche Part, der die Logik enthält: Serialize ist die Exportmethode, Deserialize die Import-Methode
VB.NET-Quellcode
- Public Class Serializer(Of T As New)
- Public Shared Function Serialize(FileName As String, Data As T, HeaderVisible As Boolean, Optional Separator As Char = ";"c) As String
- Dim HeadlineWritten As Boolean = False
- Dim ExportText As String = String.Empty
- If Data.GetType().IsGenericType AndAlso TypeOf Data Is IEnumerable Then
- For Each elem In TryCast(Data, IEnumerable)
- Dim Properties = (From prop In elem.GetType().GetProperties()
- Where Attribute.IsDefined(prop, GetType(Entity.OrderAttribute))
- Order By TryCast(prop.GetCustomAttributes(GetType(Entity.OrderAttribute), False).FirstOrDefault(), Entity.OrderAttribute).Order
- Select prop).ToList()
- If HeaderVisible AndAlso Not HeadlineWritten Then
- HeadlineWritten = True
- add(ExportText, String.Join(Separator, Properties.Select(Function(n) TryCast(n.GetCustomAttributes(GetType(Entity.ElementNameAttribute), False).FirstOrDefault(), Entity.ElementNameAttribute).ElementName).ToList()))
- End If
- add(ExportText, String.Join(Separator, Properties.Select(Function(n) n.GetValue(elem)).ToList()))
- Next
- Else
- Dim Properties = (From prop In Data.GetType().GetProperties()
- Where Attribute.IsDefined(prop, GetType(Entity.OrderAttribute))
- Order By TryCast(prop.GetCustomAttributes(GetType(Entity.OrderAttribute), False).FirstOrDefault(), Entity.OrderAttribute).Order
- Select prop).ToList()
- If HeaderVisible AndAlso Not HeadlineWritten Then
- HeadlineWritten = True
- add(ExportText, String.Join(Separator, Properties.Select(Function(n) TryCast(n, Reflection.PropertyInfo).GetCustomAttributes(GetType(Entity.ElementNameAttribute), False).FirstOrDefault()).ToList()))
- End If
- add(ExportText, String.Join(Separator, Properties.Select(Function(n) n.GetValue(n)).ToList()))
- End If
- Dim ret As String = ExportText
- Using x As New StreamWriter(FileName, False, Text.Encoding.UTF8)
- x.WriteLine(ret)
- End Using
- Return ret
- End Function
- Public Shared Function Deserialize(FileName As String, SkipFirstLine As Boolean, Optional Separator As Char = ";"c) As List(Of T)
- Dim ret As String
- Dim FirstLineSkipped As Boolean = False
- Using x As New StreamReader(FileName, Text.Encoding.UTF8)
- ret = x.ReadToEnd()
- End Using
- Dim Properties As List(Of Reflection.PropertyInfo) = (From prop In GetType(T).GetProperties()
- Where Attribute.IsDefined(prop, GetType(Entity.OrderAttribute))
- Order By TryCast(prop.GetCustomAttributes(GetType(Entity.OrderAttribute), False).FirstOrDefault(), Entity.OrderAttribute).Order
- Select prop).ToList()
- Dim Liste As New List(Of T)
- Dim elem As New T
- Dim Lines As List(Of String) = ret.Split(Chr(10), Chr(13)).ToList()
- For Each Line In Lines
- If Not String.IsNullOrWhiteSpace(Line) Then
- If SkipFirstLine AndAlso Not FirstLineSkipped Then
- FirstLineSkipped = True
- Continue For
- End If
- Dim Values As List(Of String) = Line.Split(Separator).ToList()
- Dim Counter As Integer = 0
- For Each Proper In Properties
- Select Case Proper.PropertyType
- Case GetType(String)
- GetType(T).GetProperty(Proper.Name).SetValue(elem, Values(Counter).ToString)
- Case GetType(Char)
- GetType(T).GetProperty(Proper.Name).SetValue(elem, CChar(Values(Counter)))
- Case GetType(Short)
- GetType(T).GetProperty(Proper.Name).SetValue(elem, CShort(Values(Counter)))
- Case GetType(Integer)
- GetType(T).GetProperty(Proper.Name).SetValue(elem, CInt(Values(Counter)))
- Case GetType(Long)
- GetType(T).GetProperty(Proper.Name).SetValue(elem, CLng(Values(Counter)))
- Case GetType(Single)
- GetType(T).GetProperty(Proper.Name).SetValue(elem, CSng(Values(Counter)))
- Case GetType(Double)
- GetType(T).GetProperty(Proper.Name).SetValue(elem, CDbl(Values(Counter)))
- Case GetType(Decimal)
- GetType(T).GetProperty(Proper.Name).SetValue(elem, CDec(Values(Counter)))
- Case GetType(Boolean)
- GetType(T).GetProperty(Proper.Name).SetValue(elem, CBool(Values(Counter)))
- Case GetType(Date)
- GetType(T).GetProperty(Proper.Name).SetValue(elem, CDate(Values(Counter)))
- Case GetType(Nullable(Of Short))
- GetType(T).GetProperty(Proper.Name).SetValue(elem, If(Values(Counter) Is Nothing, CType(Nothing, Nullable(Of Short)), CShort(Values(Counter))))
- Case GetType(Nullable(Of Integer))
- GetType(T).GetProperty(Proper.Name).SetValue(elem, If(Values(Counter) Is Nothing, CType(Nothing, Nullable(Of Integer)), CInt(Values(Counter))))
- Case GetType(Nullable(Of Integer))
- GetType(T).GetProperty(Proper.Name).SetValue(elem, If(Values(Counter) Is Nothing, CType(Nothing, Nullable(Of Long)), CLng(Values(Counter))))
- Case GetType(Nullable(Of Single))
- GetType(T).GetProperty(Proper.Name).SetValue(elem, If(Values(Counter) Is Nothing, CType(Nothing, Nullable(Of Single)), CSng(Values(Counter))))
- Case GetType(Nullable(Of Double))
- GetType(T).GetProperty(Proper.Name).SetValue(elem, If(Values(Counter) Is Nothing, CType(Nothing, Nullable(Of Double)), CDbl(Values(Counter))))
- Case GetType(Nullable(Of Decimal))
- GetType(T).GetProperty(Proper.Name).SetValue(elem, If(Values(Counter) Is Nothing, CType(Nothing, Nullable(Of Decimal)), CDec(Values(Counter))))
- Case GetType(Nullable(Of Boolean))
- GetType(T).GetProperty(Proper.Name).SetValue(elem, If(Values(Counter) Is Nothing, CType(Nothing, Nullable(Of Boolean)), CBool(Values(Counter))))
- Case GetType(Nullable(Of Date))
- GetType(T).GetProperty(Proper.Name).SetValue(elem, If(Values(Counter) Is Nothing, CType(Nothing, Nullable(Of Date)), CDate(Values(Counter))))
- End Select
- Counter += 1
- Next
- Liste.Add(elem)
- elem = New T
- End If
- Next
- Return Liste
- End Function
- End Class
Und anschließend legt ihr euch für jeden Importtyp eine Klasse an, die vom Basisdatenmodel erbt. Die Reihenfolge der Spalten in der CSV steuert ihr über das OrderAttribut, Spaltenüberschriften über das ElementName-Attribut.
Ein DataModel würde dann beispielsweise so aussehen:
Export zu CSV:
VB.NET-Quellcode
- Serializer(Of List(Of ExampleClass)).Serialize("C:\Temp\Testfile.csv",
- New List(Of ExampleClass) From
- {
- New ExampleClass With {.ID = 1, .Name = "Testname1", .Text = "TestText1"},
- New ExampleClass With {.ID = 2, .Name = "Testname2", .Text = "TestText2"},
- New ExampleClass With {.ID = 3, .Name = "Testname3", .Text = "TestText3"}
- }, False)
Ein Computer wird das tun, was du programmierst - nicht das, was du willst.