RSCG – Unflat
| name | Unflat |
| nuget | https://www.nuget.org/packages/Unflat/ |
| link | https://github.com/pstlnce/unflat |
| author | pstlnce |
DataReader to Object Model
This is how you can use Unflat .
The code that you start with is
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Unflat" Version="0.0.1" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>
The code that you will use is
using System;
using System.Data;
using Unflat;
namespace UnflatDemo
{
class Program
{
static void Main(string[] args)
{
// Create a DataTable and fill with sample data
var table = new DataTable();
table.Columns.Add("Id", typeof(int));
table.Columns.Add("Name", typeof(string));
table.Columns.Add("Age", typeof(int));
table.Rows.Add(1, "Andrei", 30);
table.Rows.Add(2, "Ignat", 55);
using var reader = table.CreateDataReader();
var persons = PersonParser.ReadList(reader);
foreach (var person in persons)
{
Console.WriteLine($"Id: {person.Id}, Name: {person.Name}, Age: {person.Age}");
}
}
}
}
namespace UnflatDemo
{
[Unflat.UnflatMarker]
public partial class Person
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
}
}
The code that is generated is
using System;
namespace Unflat
{
[AttributeUsage(AttributeTargets.Class)]
internal sealed class UnflatMarkerAttribute : Attribute
{
public string? ClassName { get; set; }
public MatchCase Case { get; set; } = MatchCase.All;
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
internal sealed class SettableParserAttribute : Attribute
{
/// <summary>
/// <list type="bullet">
/// <item> {0} - reader[i] </item>
/// <item> {1} - i </item>
/// <item> i - related column index </item>
/// <item> reader - reader... </item>
/// </list>
/// </summary>
/// <param name="callFormat"> an argument for string.Format(callFormat, "reader[i]", i) </param>
public SettableParserAttribute(string callFormat) {}
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
internal sealed class UnflatPrefixAttribute : Attribute
{
public UnflatPrefixAttribute(string prefix) { }
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
internal sealed class UnflatSourceAttribute : Attribute
{
public UnflatSourceAttribute(params string[] fields) {}
public UnflatSourceAttribute(int fieldOrder) {}
}
[Flags]
public enum MatchCase : int
{
None = 0,
IgnoreCase = 1,
MatchOriginal = 1 << 1,
SnakeCase = 1 << 2,
CamalCase = 1 << 3,
PascalCase = 1 << 4,
ApplyOnOverritenName = 1 << 5,
All = IgnoreCase | MatchOriginal | SnakeCase | CamalCase | PascalCase | ApplyOnOverritenName
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
internal sealed class UnflatParserAttribute : Attribute
{
/// <summary>
/// <list type="bullet">
/// <item> {0} - reader[i] </item>
/// <item> {1} - i </item>
/// <item> i - related column index </item>
/// <item> reader - reader... </item>
/// </list>
/// </summary>
/// <param name="callFormat"> an argument for string.Format(callFormat, "reader[i]", i) </param>
public string CallFormat { get; set; }
/// <summary>
/// If true than this would be a fallback way
/// for parsing reader's column value to returning type
/// if not found parsers in closest namespaces
/// </summary>
public bool IsDefault { get; set; }
/// <summary>
/// If set this value will replace the namespace that contains this parser.
/// Parser searcher will look to closest parser available to the model.
/// Parser in Test namespace, the target in Test.Test1 => matched.
/// Parser in Test.Test1.Test2, the target in Test.Test1 => not matched.
/// Parser in Test.Test1, the target in Test.Test2 => not matched, etc...
/// </summary>
public string NamespaceScope { get; set; }
}
[Serializable]
internal sealed class NotEnoughFieldsForRequiredException : Exception
{
public NotEnoughFieldsForRequiredException(int expected, int actual)
: base($"Required field/properties count: {expected}, actual reader's fields count: {actual}")
{
Expected = expected;
Actual = actual;
}
public int Expected { get; init; }
public int Actual { get; init; }
}
[Serializable]
public class MissingRequiredFieldOrPropertyException : System.Exception
{
public MissingRequiredFieldOrPropertyException(string[] propertiesOrFields)
: base("There is no matched data for required properties or fields")
{
PropertiesOrFields = propertiesOrFields;
}
public string[] PropertiesOrFields { get; init; }
}
}
using System;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace UnflatDemo
{
internal sealed partial class PersonParser
{
internal static List
where TReader : IDataReader
{
var result = new List
if(!reader.Read())
{
return result;
}
ReadSchemaIndexes(reader, out int col_Age, out int col_Name, out int col_Id);
do
{
UnflatDemo.Person parsed = new UnflatDemo.Person();
if(col_Age != -1)
{
parsed.Age = reader[col_Age] is int pcol_Age ? pcol_Age : default;
}
if(col_Name != -1)
{
parsed.Name = reader[col_Name] is string pcol_Name ? pcol_Name : default;
}
if(col_Id != -1)
{
parsed.Id = reader[col_Id] is int pcol_Id ? pcol_Id : default;
}
result.Add(parsed);
} while(reader.Read());
return result;
}
internal static IEnumerable
where TReader : IDataReader
{
if(!reader.Read())
{
yield break;
}
ReadSchemaIndexes(reader, out int col_Age, out int col_Name, out int col_Id);
do
{
UnflatDemo.Person parsed = new UnflatDemo.Person();
if(col_Age != -1)
{
parsed.Age = reader[col_Age] is int pcol_Age ? pcol_Age : default;
}
if(col_Name != -1)
{
parsed.Name = reader[col_Name] is string pcol_Name ? pcol_Name : default;
}
if(col_Id != -1)
{
parsed.Id = reader[col_Id] is int pcol_Id ? pcol_Id : default;
}
yield return parsed;
} while(reader.Read());
}
internal static async Task> ReadListAsync
where TReader : DbDataReader
{
var result = new List
if(!(await reader.ReadAsync(token).ConfigureAwait(false)))
{
return result;
}
ReadSchemaIndexes(reader, out int col_Age, out int col_Name, out int col_Id);
Task
while(true)
{
UnflatDemo.Person parsed = new UnflatDemo.Person();
if(col_Age != -1)
{
parsed.Age = reader[col_Age] is int pcol_Age ? pcol_Age : default;
}
if(col_Name != -1)
{
parsed.Name = reader[col_Name] is string pcol_Name ? pcol_Name : default;
}
if(col_Id != -1)
{
parsed.Id = reader[col_Id] is int pcol_Id ? pcol_Id : default;
}
reading = reader.ReadAsync(token);
result.Add(parsed);
if(!(await reading.ConfigureAwait(false)))
{
break;
}
}
return result;
}
internal static async ValueTask> ReadListAsyncValue
where TReader : DbDataReader
{
var result = new List
if(!(await reader.ReadAsync(token).ConfigureAwait(false)))
{
return result;
}
ReadSchemaIndexes(reader, out int col_Age, out int col_Name, out int col_Id);
Task
while(true)
{
UnflatDemo.Person parsed = new UnflatDemo.Person();
if(col_Age != -1)
{
parsed.Age = reader[col_Age] is int pcol_Age ? pcol_Age : default;
}
if(col_Name != -1)
{
parsed.Name = reader[col_Name] is string pcol_Name ? pcol_Name : default;
}
if(col_Id != -1)
{
parsed.Id = reader[col_Id] is int pcol_Id ? pcol_Id : default;
}
reading = reader.ReadAsync(token);
result.Add(parsed);
if(!(await reading.ConfigureAwait(false)))
{
break;
}
}
return result;
}
internal static async IAsyncEnumerable
where TReader : DbDataReader
{
if(!(await reader.ReadAsync(token).ConfigureAwait(false)))
{
yield break;
}
ReadSchemaIndexes(reader, out int col_Age, out int col_Name, out int col_Id);
Task
while(true)
{
UnflatDemo.Person parsed = new UnflatDemo.Person();
if(col_Age != -1)
{
parsed.Age = reader[col_Age] is int pcol_Age ? pcol_Age : default;
}
if(col_Name != -1)
{
parsed.Name = reader[col_Name] is string pcol_Name ? pcol_Name : default;
}
if(col_Id != -1)
{
parsed.Id = reader[col_Id] is int pcol_Id ? pcol_Id : default;
}
reading = reader.ReadAsync(token);
yield return parsed;
if(!(await reading.ConfigureAwait(false)))
{
break;
}
}
}
public static void ReadSchemaIndexes
where TReader : IDataReader
{
col_Age = -1;
col_Name = -1;
col_Id = -1;
var fieldCount = reader.FieldCount;
for(int i = 0; i < fieldCount; i++)
{
ReadSchemaIndex(reader.GetName(i), i, ref col_Age, ref col_Name, ref col_Id);
}
}
public static void ReadSchemaIndex(string name, int i, ref int col_Age, ref int col_Name, ref int col_Id)
{
switch(name.Length)
{
case 2:
if(col_Id == -1 && string.Equals("Id", name, StringComparison.OrdinalIgnoreCase))
{
col_Id = i;
}
break;
case 3:
if(col_Age == -1 && string.Equals("Age", name, StringComparison.OrdinalIgnoreCase))
{
col_Age = i;
}
break;
case 4:
if(col_Name == -1 && string.Equals("Name", name, StringComparison.OrdinalIgnoreCase))
{
col_Name = i;
}
break;
default:
break;
}
}
}
}
[/code]
Code and pdf at
https://ignatandrei.github.io/RSCG_Examples/v2/docs/Unflat