diff --git a/MapDataReader.Benchmarks/MapDataReader.Benchmarks.csproj b/MapDataReader.Benchmarks/MapDataReader.Benchmarks.csproj
index 1073b53..44cba17 100644
--- a/MapDataReader.Benchmarks/MapDataReader.Benchmarks.csproj
+++ b/MapDataReader.Benchmarks/MapDataReader.Benchmarks.csproj
@@ -5,6 +5,7 @@
net6.0
enable
enable
+ latest
diff --git a/MapDataReader.Benchmarks/Program.cs b/MapDataReader.Benchmarks/Program.cs
index d402e0c..bf3afe3 100644
--- a/MapDataReader.Benchmarks/Program.cs
+++ b/MapDataReader.Benchmarks/Program.cs
@@ -93,7 +93,7 @@ public static void Setup()
}
}
- [GenerateDataReaderMapper]
+ [GenerateDataReaderMapper(AccessModifier = "internal")]
public class TestClass
{
public string String1 { get; set; }
diff --git a/MapDataReader.Tests/MapDataReader.Tests.csproj b/MapDataReader.Tests/MapDataReader.Tests.csproj
index b14646f..033eb5a 100644
--- a/MapDataReader.Tests/MapDataReader.Tests.csproj
+++ b/MapDataReader.Tests/MapDataReader.Tests.csproj
@@ -4,8 +4,8 @@
net6.0
enable
enable
-
false
+ latest
diff --git a/MapDataReader.Tests/TestActualCode.cs b/MapDataReader.Tests/TestActualCode.cs
index 76ccaca..2c71391 100644
--- a/MapDataReader.Tests/TestActualCode.cs
+++ b/MapDataReader.Tests/TestActualCode.cs
@@ -3,6 +3,7 @@
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Reflection;
using System.Text;
using System.Threading.Tasks;
@@ -147,7 +148,7 @@ public void TestStringAssign()
}
[TestMethod]
- public void TestDatatReader()
+ public void TestDataReader()
{
//create datatable with test data
var dt = new DataTable();
@@ -227,6 +228,24 @@ public void TestWrongProperty()
o.SetPropertyByName("Name", 123); //try to assign string prop to int
Assert.IsTrue(o.Name == null); //wrong type. should be null
}
+
+ [TestMethod]
+ public void TestInternalAccessModifier()
+ {
+ var type = typeof(MapperExtensions);
+ var method = type.GetMethod("ToTestClassInternal", BindingFlags.Static | BindingFlags.NonPublic);
+
+ Assert.IsNotNull(method, "Expected method 'ToTestClassInternal' to be 'internal'.");
+ }
+
+ [TestMethod]
+ public void TestInternalAccessModifierNamed()
+ {
+ var type = typeof(MapperExtensions);
+ var method = type.GetMethod("ToTestClassInternalNamed", BindingFlags.Static | BindingFlags.NonPublic);
+
+ Assert.IsNotNull(method, "Expected method 'ToTestClassInternalNamed' to be 'internal'.");
+ }
}
public class BaseClass
@@ -239,5 +258,17 @@ public class ChildClass : BaseClass
{
public string Name { get; set; }
}
+
+ [GenerateDataReaderMapper("internal")]
+ internal class TestClassInternal
+ {
+ public int Id { get; set; }
+ }
+
+ [GenerateDataReaderMapper(AccessModifier = "internal")]
+ internal class TestClassInternalNamed
+ {
+ public int Id { get; set; }
+ }
}
diff --git a/MapDataReader.Tests/TestGenerator.cs b/MapDataReader.Tests/TestGenerator.cs
index 083790b..52624a8 100644
--- a/MapDataReader.Tests/TestGenerator.cs
+++ b/MapDataReader.Tests/TestGenerator.cs
@@ -29,6 +29,52 @@ public class MyClass
public decimal Price {get;set;}
}
}
+";
+ var src = GetAndCheckOutputSource(userSource);
+ }
+
+ [TestMethod]
+ public void TestAccessModifier()
+ {
+ string userSource = @"
+using MapDataReader;
+
+namespace MyCode
+{
+ [GenerateDataReaderMapper(""internal"")]
+ public class MyClass
+ {
+ public string Name {get;set;}
+ public int Size {get;set;}
+ public bool Enabled {get;set;}
+ public System.DateTime Created {get;set;}
+ public System.DateTimeOffset Offset {get;set;}
+ public decimal Price {get;set;}
+ }
+}
+";
+ var src = GetAndCheckOutputSource(userSource);
+ }
+
+ [TestMethod]
+ public void TestAttributes()
+ {
+ string userSource = @"
+using MapDataReader;
+
+namespace TestNamespace
+{
+ [GenerateDataReaderMapper(AccessModifier = ""internal"", NamespaceName = ""TestNamespace"", MethodName = ""ConvertToCustom"")]
+ public class MyClass
+ {
+ public string Name {get;set;}
+ public int Size {get;set;}
+ public bool Enabled {get;set;}
+ public System.DateTime Created {get;set;}
+ public System.DateTimeOffset Offset {get;set;}
+ public decimal Price {get;set;}
+ }
+}
";
var src = GetAndCheckOutputSource(userSource);
}
diff --git a/MapDataReader/GenerateDataReaderMapperAttribute.cs b/MapDataReader/GenerateDataReaderMapperAttribute.cs
new file mode 100644
index 0000000..491574b
--- /dev/null
+++ b/MapDataReader/GenerateDataReaderMapperAttribute.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace MapDataReader;
+
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+public class GenerateDataReaderMapperAttribute : Attribute
+{
+ public string AccessModifier { get; set; }
+
+ ///
+ /// Gets or sets the namespace to be used in the generated methods.
+ ///
+ public string NamespaceName { get; set; }
+
+ ///
+ /// Gets or sets the method name to be used in the generated methods.
+ ///
+ public string MethodName { get; set; }
+
+ public GenerateDataReaderMapperAttribute()
+ {
+ AccessModifier = "public";
+ }
+
+ public GenerateDataReaderMapperAttribute(string access = "public")
+ {
+ AccessModifier = access;
+ }
+}
\ No newline at end of file
diff --git a/MapDataReader/Helpers.cs b/MapDataReader/Helpers.cs
new file mode 100644
index 0000000..301a283
--- /dev/null
+++ b/MapDataReader/Helpers.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace MapDataReader;
+
+internal static class Helpers
+{
+ internal static bool IsDecoratedWithAttribute(this TypeDeclarationSyntax cdecl, string attributeName) =>
+ cdecl.AttributeLists
+ .SelectMany(x => x.Attributes)
+ .Any(x => x.Name.ToString().Contains(attributeName));
+
+
+ internal static string FullName(this ITypeSymbol typeSymbol) => typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+
+ internal static string StringConcat(this IEnumerable source, string separator) => string.Join(separator, source);
+
+ // returns all properties with public setters
+ internal static IEnumerable GetAllSettableProperties(this ITypeSymbol typeSymbol)
+ {
+ var result = typeSymbol
+ .GetMembers()
+ .Where(s => s.Kind == SymbolKind.Property).Cast() //get all properties
+ .Where(p => p.SetMethod?.DeclaredAccessibility == Accessibility.Public) //has a public setter?
+ .ToList();
+
+ //now get the base class
+ var baseType = typeSymbol.BaseType;
+ if (baseType != null)
+ result.AddRange(baseType.GetAllSettableProperties()); //recursion
+
+ return result;
+ }
+
+ //checks if type is a nullable num
+ internal static bool IsNullableEnum(this ITypeSymbol symbol)
+ {
+ //tries to get underlying non-nullable type from nullable type
+ //and then check if it's Enum
+ if (symbol.NullableAnnotation == NullableAnnotation.Annotated
+ && symbol is INamedTypeSymbol namedType
+ && namedType.IsValueType
+ && namedType.IsGenericType
+ && namedType.ConstructedFrom?.ToDisplayString() == "System.Nullable"
+ )
+ return namedType.TypeArguments[0].TypeKind == TypeKind.Enum;
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/MapDataReader/MapDataReader.csproj b/MapDataReader/MapDataReader.csproj
index 7fda524..286c9e4 100644
--- a/MapDataReader/MapDataReader.csproj
+++ b/MapDataReader/MapDataReader.csproj
@@ -14,6 +14,7 @@
aot;source-generator
Super fast mapping of DataReader to custom objects
True
+ latest
diff --git a/MapDataReader/MapperGenerator.cs b/MapDataReader/MapperGenerator.cs
index 07e3b91..cceba2e 100644
--- a/MapDataReader/MapperGenerator.cs
+++ b/MapDataReader/MapperGenerator.cs
@@ -6,29 +6,30 @@
using System.Collections.Immutable;
using System.Linq;
-namespace MapDataReader
+namespace MapDataReader;
+
+[Generator]
+public class MapperGenerator : ISourceGenerator
{
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
- public class GenerateDataReaderMapperAttribute : Attribute
+ public void Execute(GeneratorExecutionContext context)
{
- }
+ var targetTypeTracker = context.SyntaxContextReceiver as TargetTypeTracker;
- [Generator]
- public class MapperGenerator : ISourceGenerator
- {
- public void Execute(GeneratorExecutionContext context)
+ foreach (var typeNode in targetTypeTracker.TypesNeedingGening)
{
- var targetTypeTracker = context.SyntaxContextReceiver as TargetTypeTracker;
+ var typeNodeSymbol = context.Compilation
+ .GetSemanticModel(typeNode.SyntaxTree)
+ .GetDeclaredSymbol(typeNode);
- foreach (var typeNode in targetTypeTracker.TypesNeedingGening)
- {
- var typeNodeSymbol = context.Compilation
- .GetSemanticModel(typeNode.SyntaxTree)
- .GetDeclaredSymbol(typeNode);
+ var allProperties = typeNodeSymbol.GetAllSettableProperties();
- var allProperties = typeNodeSymbol.GetAllSettableProperties();
+ var allAttributes = GetGeneratedMapperAttributes(typeNode);
- var src = $@"
+ var accessModifier = allAttributes["AccessModifier"];
+ var namespaceName = allAttributes["NamespaceName"];
+ var methodName = allAttributes["MethodName"] ?? $"To{typeNode.Identifier}";
+
+ var src = $@"
//
#pragma warning disable 8019 //disable 'unnecessary using directive' warning
using System;
@@ -36,11 +37,11 @@ public void Execute(GeneratorExecutionContext context)
using System.Linq;
using System.Collections.Generic; //to support List etc
- namespace MapDataReader
- {{
+ namespace {namespaceName};
+
public static partial class MapperExtensions
{{
- public static void SetPropertyByName(this {typeNodeSymbol.FullName()} target, string name, object value)
+ {accessModifier} static void SetPropertyByName(this {typeNodeSymbol.FullName()} target, string name, object value)
{{
SetPropertyByUpperName(target, name.ToUpperInvariant(), value);
}}
@@ -75,11 +76,11 @@ private static void SetPropertyByUpperName(this {typeNodeSymbol.FullName()} targ
}} //end method";
- if (typeNodeSymbol.InstanceConstructors.Any(c => !c.Parameters.Any())) //has a constructor without parameters?
- {
- src += $@"
+ if (typeNodeSymbol.InstanceConstructors.Any(c => !c.Parameters.Any())) //has a constructor without parameters?
+ {
+ src += $@"
- public static List<{typeNodeSymbol.FullName()}> To{typeNode.Identifier}(this IDataReader dr)
+ {accessModifier} static List<{typeNodeSymbol.FullName()}> {methodName}(this IDataReader dr)
{{
var list = new List<{typeNodeSymbol.FullName()}>();
@@ -105,77 +106,63 @@ private static void SetPropertyByUpperName(this {typeNodeSymbol.FullName()} targ
dr.Close();
return list;
}}";
- }
-
- src += "\n}"; //end class
- src += "\n}"; //end namespace
-
- // Add the source code to the compilation
- context.AddSource($"{typeNodeSymbol.Name}DataReaderMapper.g.cs", src);
}
- }
- public void Initialize(GeneratorInitializationContext context)
- {
- context.RegisterForSyntaxNotifications(() => new TargetTypeTracker());
+ src += "\n}"; //end class
+
+ // Add the source code to the compilation
+ context.AddSource($"{typeNodeSymbol.Name}DataReaderMapper.g.cs", src);
}
}
- internal class TargetTypeTracker : ISyntaxContextReceiver
+ public void Initialize(GeneratorInitializationContext context)
{
- public IImmutableList TypesNeedingGening = ImmutableList.Create();
-
- public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
- {
- if (context.Node is ClassDeclarationSyntax cdecl)
- if (cdecl.IsDecoratedWithAttribute("GenerateDataReaderMapper"))
- TypesNeedingGening = TypesNeedingGening.Add(cdecl);
- }
+ context.RegisterForSyntaxNotifications(() => new TargetTypeTracker());
}
- internal static class Helpers
+ private static Dictionary GetGeneratedMapperAttributes(ClassDeclarationSyntax typeNode)
{
- internal static bool IsDecoratedWithAttribute(this TypeDeclarationSyntax cdecl, string attributeName) =>
- cdecl.AttributeLists
- .SelectMany(x => x.Attributes)
- .Any(x => x.Name.ToString().Contains(attributeName));
-
-
- internal static string FullName(this ITypeSymbol typeSymbol) => typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
-
- internal static string StringConcat(this IEnumerable source, string separator) => string.Join(separator, source);
-
- // returns all properties with public setters
- internal static IEnumerable GetAllSettableProperties(this ITypeSymbol typeSymbol)
+ // Default attribute values
+ var defaults = new Dictionary
{
- var result = typeSymbol
- .GetMembers()
- .Where(s => s.Kind == SymbolKind.Property).Cast() //get all properties
- .Where(p => p.SetMethod?.DeclaredAccessibility == Accessibility.Public) //has a public setter?
- .ToList();
-
- //now get the base class
- var baseType = typeSymbol.BaseType;
- if (baseType != null)
- result.AddRange(baseType.GetAllSettableProperties()); //recursion
-
- return result;
+ { "AccessModifier", "public" },
+ { "NamespaceName", "MapDataReader" },
+ { "MethodName", null }
+ };
+
+ // Retrieve the attribute list
+ var attributeList = typeNode.AttributeLists
+ .SelectMany(al => al.Attributes)
+ .FirstOrDefault(attr => attr.Name.ToString() == "GenerateDataReaderMapper");
+
+ if (attributeList?.ArgumentList == null)
+ return defaults;
+
+ var arguments = attributeList.ArgumentList.Arguments;
+
+ if (arguments.Count == 0)
+ return defaults;
+
+ if (arguments.Count == 1)
+ {
+ var argument = arguments[0];
+ if (argument.NameEquals is null)
+ {
+ if (argument.Expression is LiteralExpressionSyntax argumentExpr)
+ defaults["AccessModifier"] = argumentExpr.Token.ValueText;
+ }
}
- //checks if type is a nullable num
- internal static bool IsNullableEnum(this ITypeSymbol symbol)
+ foreach (var argument in arguments)
{
- //tries to get underlying non-nullable type from nullable type
- //and then check if it's Enum
- if (symbol.NullableAnnotation == NullableAnnotation.Annotated
- && symbol is INamedTypeSymbol namedType
- && namedType.IsValueType
- && namedType.IsGenericType
- && namedType.ConstructedFrom?.ToDisplayString() == "System.Nullable"
- )
- return namedType.TypeArguments[0].TypeKind == TypeKind.Enum;
-
- return false;
+ var name = argument.NameEquals?.Name.Identifier.Text;
+
+ if (name == null || !defaults.ContainsKey(name)) continue;
+
+ if (argument.Expression is LiteralExpressionSyntax argumentExpr)
+ defaults[name] = argumentExpr.Token.ValueText;
}
+
+ return defaults;
}
}
\ No newline at end of file
diff --git a/MapDataReader/TargetTypeTracker.cs b/MapDataReader/TargetTypeTracker.cs
new file mode 100644
index 0000000..7cf21dd
--- /dev/null
+++ b/MapDataReader/TargetTypeTracker.cs
@@ -0,0 +1,18 @@
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace MapDataReader;
+
+internal class TargetTypeTracker : ISyntaxContextReceiver
+{
+ public IImmutableList TypesNeedingGening = ImmutableList.Create();
+
+ public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
+ {
+ if (context.Node is not ClassDeclarationSyntax classDec) return;
+
+ if (classDec.IsDecoratedWithAttribute("GenerateDataReaderMapper"))
+ TypesNeedingGening = TypesNeedingGening.Add(classDec);
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index ab78f76..21c89e8 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,20 @@ Some notes for the above
* Properly maps `DBNull` to `null`.
* Complex-type properties may not work.
+### Access Modifier: `public` or `internal`
+
+You can now specify the access modifer to be used with the mapping methods. By default, the methods will be `public` for backwards compatability.
+
+For example, to prevent exposure outside your assembly you'd set it to `internal`. This would hide the mapping methods outside your model project:
+
+``` csharp
+[GenerateDataReaderMapper("internal")]
+public class MyClass
+{
+ public int ID { get; set; }
+...
+```
+
## Bonus API: `SetPropertyByName`
This package also adds a super fast `SetPropertyByName` extension method generated at compile time for your class.