Skip to content

Commit 5d97159

Browse files
committed
update to .net 7, add source generator for reader factories
1 parent 0f9572d commit 5d97159

File tree

62 files changed

+1587
-250
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1587
-250
lines changed

FluentCommand.sln

+13
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.Json", "src\F
4141
EndProject
4242
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.Dapper", "src\FluentCommand.Dapper\FluentCommand.Dapper.csproj", "{47EE3C3A-178C-4AE5-8A54-F3791FA76E08}"
4343
EndProject
44+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.Generators", "src\FluentCommand.Generators\FluentCommand.Generators.csproj", "{43DCCD08-D585-4905-95A9-120D300C512A}"
45+
EndProject
46+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.Generators.Tests", "test\FluentCommand.Generators.Tests\FluentCommand.Generators.Tests.csproj", "{00F44ED1-561E-43DC-ABC1-A78411FFC6F3}"
47+
EndProject
4448
Global
4549
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4650
Debug|Any CPU = Debug|Any CPU
@@ -95,6 +99,14 @@ Global
9599
{47EE3C3A-178C-4AE5-8A54-F3791FA76E08}.Debug|Any CPU.Build.0 = Debug|Any CPU
96100
{47EE3C3A-178C-4AE5-8A54-F3791FA76E08}.Release|Any CPU.ActiveCfg = Release|Any CPU
97101
{47EE3C3A-178C-4AE5-8A54-F3791FA76E08}.Release|Any CPU.Build.0 = Release|Any CPU
102+
{43DCCD08-D585-4905-95A9-120D300C512A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
103+
{43DCCD08-D585-4905-95A9-120D300C512A}.Debug|Any CPU.Build.0 = Debug|Any CPU
104+
{43DCCD08-D585-4905-95A9-120D300C512A}.Release|Any CPU.ActiveCfg = Release|Any CPU
105+
{43DCCD08-D585-4905-95A9-120D300C512A}.Release|Any CPU.Build.0 = Release|Any CPU
106+
{00F44ED1-561E-43DC-ABC1-A78411FFC6F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
107+
{00F44ED1-561E-43DC-ABC1-A78411FFC6F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
108+
{00F44ED1-561E-43DC-ABC1-A78411FFC6F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
109+
{00F44ED1-561E-43DC-ABC1-A78411FFC6F3}.Release|Any CPU.Build.0 = Release|Any CPU
98110
EndGlobalSection
99111
GlobalSection(SolutionProperties) = preSolution
100112
HideSolutionNode = FALSE
@@ -106,6 +118,7 @@ Global
106118
{CCC34DD0-4706-44CB-AC03-A0E607F449A2} = {228D7B65-A886-4D86-AED0-F3CB0C61819B}
107119
{A003BB6E-286D-4F52-B980-992816892CEE} = {228D7B65-A886-4D86-AED0-F3CB0C61819B}
108120
{CFCFEA34-ABFA-490E-85BF-A7DC12392245} = {228D7B65-A886-4D86-AED0-F3CB0C61819B}
121+
{00F44ED1-561E-43DC-ABC1-A78411FFC6F3} = {228D7B65-A886-4D86-AED0-F3CB0C61819B}
109122
EndGlobalSection
110123
GlobalSection(ExtensibilityGlobals) = postSolution
111124
SolutionGuid = {F155C146-9B35-439E-ADB6-420C772E18F9}

appveyor.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: 9.5.{build}
1+
version: 10.0.{build}
22
os: Visual Studio 2022
33

44
environment:

src/Directory.Build.props

+5-5
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@
3232
</PropertyGroup>
3333

3434
<PropertyGroup>
35-
<Version>9.0.0.0</Version>
36-
<PackageVersion>9.0.0.0</PackageVersion>
37-
<AssemblyVersion>9.0.0.0</AssemblyVersion>
38-
<FileVersion>9.0.0.0</FileVersion>
39-
<InformationalVersion>9.0.0.0</InformationalVersion>
35+
<Version>10.0.0</Version>
36+
<PackageVersion>10.0.0</PackageVersion>
37+
<AssemblyVersion>10.0.0.0</AssemblyVersion>
38+
<FileVersion>10.0.0.0</FileVersion>
39+
<InformationalVersion>10.0.0</InformationalVersion>
4040
</PropertyGroup>
4141

4242
<ItemGroup>

src/FluentCommand.Batch/FluentCommand.Batch.csproj

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
3+
<TargetFrameworks>netstandard2.0;net6.0;net7.0</TargetFrameworks>
44
</PropertyGroup>
55

66
<PropertyGroup>
@@ -9,8 +9,8 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
13-
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2" />
12+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
13+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
1414
</ItemGroup>
1515

1616
<ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1+
using System.Data;
2+
using System.Data.Common;
3+
4+
using Dapper;
5+
6+
using static Dapper.SqlMapper;
7+
18
namespace FluentCommand;
29

310
/// <summary>
411
/// Extension methods for <see cref="IDataCommand"/>
512
/// </summary>
613
public static class DataCommandExtensions
714
{
8-
/// <summary>
9-
/// Executes the command against the connection and converts the results to dynamic objects.
10-
/// </summary>
11-
/// <param name="dataQuery">The <see cref="IDataQuery"/> for this extension method.</param>
12-
/// <returns>
13-
/// An <see cref="T:System.Collections.Generic.IEnumerable`1" /> of dynamic objects.
14-
/// </returns>
15-
public static IEnumerable<dynamic> Query(this IDataQuery dataQuery)
16-
{
17-
return dataQuery.Query(ReaderFactory.DynamicFactory);
18-
}
1915

2016
/// <summary>
2117
/// Executes the command against the connection and converts the results to <typeparamref name="TEntity" /> objects.
@@ -28,20 +24,20 @@ public static IEnumerable<dynamic> Query(this IDataQuery dataQuery)
2824
public static IEnumerable<TEntity> Query<TEntity>(this IDataQuery dataQuery)
2925
where TEntity : class, new()
3026
{
31-
return dataQuery.Query(ReaderFactory.EntityFactory<TEntity>);
32-
}
27+
var results = new List<TEntity>();
3328

29+
dataQuery.Read(reader =>
30+
{
31+
var parser = reader.GetRowParser<TEntity>();
3432

35-
/// <summary>
36-
/// Executes the query and returns the first row in the result as a dynamic object.
37-
/// </summary>
38-
/// <param name="dataQuery">The <see cref="IDataQuery"/> for this extension method.</param>
39-
/// <returns>
40-
/// A instance of a dynamic object if row exists; otherwise null.
41-
/// </returns>
42-
public static dynamic QuerySingle(this IDataQuery dataQuery)
43-
{
44-
return dataQuery.QuerySingle(ReaderFactory.DynamicFactory);
33+
while (reader.Read())
34+
{
35+
var entity = parser(reader);
36+
results.Add(entity);
37+
}
38+
}, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult);
39+
40+
return results;
4541
}
4642

4743
/// <summary>
@@ -55,23 +51,20 @@ public static dynamic QuerySingle(this IDataQuery dataQuery)
5551
public static TEntity QuerySingle<TEntity>(this IDataQuery dataQuery)
5652
where TEntity : class
5753
{
58-
return dataQuery.QuerySingle(ReaderFactory.EntityFactory<TEntity>);
59-
}
54+
TEntity result = default;
6055

56+
dataQuery.Read(reader =>
57+
{
58+
var parser = reader.GetRowParser<TEntity>();
59+
if (reader.Read())
60+
result = parser(reader);
6161

62-
/// <summary>
63-
/// Executes the command against the connection and converts the results to dynamic objects asynchronously.
64-
/// </summary>
65-
/// <param name="dataQuery">The <see cref="IDataQueryAsync"/> for this extension method.</param>
66-
/// <param name="cancellationToken">The cancellation instruction.</param>
67-
/// <returns>
68-
/// An <see cref="T:System.Collections.Generic.IEnumerable`1" /> of dynamic objects.
69-
/// </returns>
70-
public static Task<IEnumerable<dynamic>> QueryAsync(this IDataQueryAsync dataQuery, CancellationToken cancellationToken = default(CancellationToken))
71-
{
72-
return dataQuery.QueryAsync(ReaderFactory.DynamicFactory, cancellationToken);
62+
}, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow);
63+
64+
return result;
7365
}
7466

67+
7568
/// <summary>
7669
/// Executes the command against the connection and converts the results to <typeparamref name="TEntity" /> objects asynchronously.
7770
/// </summary>
@@ -81,24 +74,34 @@ public static TEntity QuerySingle<TEntity>(this IDataQuery dataQuery)
8174
/// <returns>
8275
/// An <see cref="T:System.Collections.Generic.IEnumerable`1" /> of <typeparamref name="TEntity" /> objects.
8376
/// </returns>
84-
public static Task<IEnumerable<TEntity>> QueryAsync<TEntity>(this IDataQueryAsync dataQuery, CancellationToken cancellationToken = default(CancellationToken))
77+
public static async Task<IEnumerable<TEntity>> QueryAsync<TEntity>(this IDataQueryAsync dataQuery, CancellationToken cancellationToken = default(CancellationToken))
8578
where TEntity : class
8679
{
87-
return dataQuery.QueryAsync(ReaderFactory.EntityFactory<TEntity>, cancellationToken);
88-
}
80+
var results = new List<TEntity>();
8981

82+
await dataQuery.ReadAsync(async (reader, token) =>
83+
{
84+
var parser = reader.GetRowParser<TEntity>();
9085

91-
/// <summary>
92-
/// Executes the query and returns the first row in the result as a dynamic object asynchronously.
93-
/// </summary>
94-
/// <param name="dataQuery">The <see cref="IDataQueryAsync"/> for this extension method.</param>
95-
/// <param name="cancellationToken">The cancellation instruction.</param>
96-
/// <returns>
97-
/// A instance of a dynamic object if row exists; otherwise null.
98-
/// </returns>
99-
public static Task<dynamic> QuerySingleAsync(this IDataQueryAsync dataQuery, CancellationToken cancellationToken = default(CancellationToken))
100-
{
101-
return dataQuery.QuerySingleAsync(ReaderFactory.DynamicFactory, cancellationToken);
86+
if (reader is DbDataReader dataReader)
87+
{
88+
while (await dataReader.ReadAsync(token))
89+
{
90+
var entity = parser(reader);
91+
results.Add(entity);
92+
}
93+
}
94+
else
95+
{
96+
while (reader.Read())
97+
{
98+
var entity = parser(reader);
99+
results.Add(entity);
100+
}
101+
}
102+
}, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancellationToken);
103+
104+
return results;
102105
}
103106

104107
/// <summary>
@@ -110,9 +113,29 @@ public static TEntity QuerySingle<TEntity>(this IDataQuery dataQuery)
110113
/// <returns>
111114
/// A instance of <typeparamref name="TEntity" /> if row exists; otherwise null.
112115
/// </returns>
113-
public static Task<TEntity> QuerySingleAsync<TEntity>(this IDataQueryAsync dataQuery, CancellationToken cancellationToken = default(CancellationToken))
116+
public static async Task<TEntity> QuerySingleAsync<TEntity>(this IDataQueryAsync dataQuery, CancellationToken cancellationToken = default(CancellationToken))
114117
where TEntity : class
115118
{
116-
return dataQuery.QuerySingleAsync(ReaderFactory.EntityFactory<TEntity>, cancellationToken);
119+
TEntity result = default;
120+
121+
await dataQuery.ReadAsync(async (reader, token) =>
122+
{
123+
var parser = reader.GetRowParser<TEntity>();
124+
125+
if (reader is DbDataReader dataReader)
126+
{
127+
if (await dataReader.ReadAsync(token))
128+
result = parser(reader);
129+
}
130+
else
131+
{
132+
if (reader.Read())
133+
result = parser(reader);
134+
}
135+
136+
137+
}, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow, cancellationToken);
138+
139+
return result;
117140
}
118141
}

src/FluentCommand.Dapper/FluentCommand.Dapper.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
4+
<TargetFrameworks>netstandard2.0;net6.0;net7.0</TargetFrameworks>
55
<RootNamespace>FluentCommand</RootNamespace>
66
</PropertyGroup>
77

src/FluentCommand.Dapper/ReaderFactory.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static class ReaderFactory
1616
/// </summary>
1717
/// <typeparam name="TEntity">The type of the entity.</typeparam>
1818
/// <param name="reader">The open <see cref="IDataReader" /> to get the object from.</param>
19-
/// <returns>A TEntity object having property names set that match the field names in the <see cref="IDataReader" />.</returns>
19+
/// <returns>A TEntity object having property names set that match the field names in the <paramref name="reader"/> <see cref="IDataReader" />.</returns>
2020
public static TEntity EntityFactory<TEntity>(IDataReader reader)
2121
where TEntity : class
2222
{

src/FluentCommand.EntityFactory/FluentCommand.EntityFactory.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
3+
<TargetFrameworks>netstandard2.0;net6.0;net7.0</TargetFrameworks>
44
<RootNamespace>FluentCommand</RootNamespace>
55
</PropertyGroup>
66

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System.Collections.Immutable;
2+
3+
using Microsoft.CodeAnalysis;
4+
using Microsoft.CodeAnalysis.CSharp;
5+
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
7+
namespace FluentCommand.Generators;
8+
9+
[Generator(LanguageNames.CSharp)]
10+
public class DataReaderFactoryGenerator : IIncrementalGenerator
11+
{
12+
public void Initialize(IncrementalGeneratorInitializationContext context)
13+
{
14+
var provider = context.SyntaxProvider.ForAttributeWithMetadataName(
15+
fullyQualifiedMetadataName: "FluentCommand.GenerateDataReaderAttribute",
16+
predicate: SyntacticPredicate,
17+
transform: SemanticTransform
18+
);
19+
20+
context.RegisterSourceOutput(provider, Execute);
21+
}
22+
23+
private static void Execute(SourceProductionContext context, EntityClass entityClass)
24+
{
25+
var qualifiedName = entityClass.EntityNamespace is null
26+
? entityClass.EntityName
27+
: $"{entityClass.EntityNamespace}.{entityClass.EntityName}";
28+
29+
var source = DataReaderFactoryWriter.Generate(entityClass);
30+
31+
context.AddSource($"{qualifiedName}.DataReaderFactory.g.cs", source);
32+
}
33+
34+
private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken cancellationToken)
35+
{
36+
return syntaxNode is ClassDeclarationSyntax { AttributeLists.Count: > 0 } classDeclaration && !classDeclaration.Modifiers.Any(SyntaxKind.AbstractKeyword)
37+
|| syntaxNode is RecordDeclarationSyntax { AttributeLists.Count: > 0 } recordDeclaration && !recordDeclaration.Modifiers.Any(SyntaxKind.AbstractKeyword);
38+
}
39+
40+
private static EntityClass SemanticTransform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken)
41+
{
42+
if (context.TargetSymbol is not INamedTypeSymbol targetSymbol)
43+
return null;
44+
45+
var classNamespace = targetSymbol.ContainingNamespace.ToDisplayString();
46+
var className = targetSymbol.Name;
47+
48+
var properties = targetSymbol
49+
.GetMembers()
50+
.Where(m => m.Kind == SymbolKind.Property)
51+
.OfType<IPropertySymbol>()
52+
.Where(IsIncluded)
53+
.Select(p => new EntityProperty(p.Name, p.Type.ToDisplayString()))
54+
.ToImmutableArray();
55+
56+
return new EntityClass(InitializationMode.ObjectInitializer, classNamespace, className, properties);
57+
}
58+
59+
public static bool IsIncluded(IPropertySymbol propertySymbol)
60+
{
61+
var attributes = propertySymbol.GetAttributes();
62+
if (attributes.Any(a => a.AttributeClass?.ToDisplayString() == "System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute"))
63+
return false;
64+
65+
return !propertySymbol.IsIndexer && !propertySymbol.IsAbstract;
66+
}
67+
}

0 commit comments

Comments
 (0)