Skip to content

Commit 4899c53

Browse files
committed
Add project files.
1 parent a58893a commit 4899c53

11 files changed

Lines changed: 436 additions & 0 deletions
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using Microsoft.VisualStudio.TestTools.UnitTesting;
3+
4+
namespace NBS.EntityFrameworkCore.SqlServer.Tests
5+
{
6+
public abstract class IntegrationTestBase
7+
{
8+
protected TestDbContext? DbContext { get; private set; }
9+
10+
[TestInitialize]
11+
public void SetUp()
12+
{
13+
var dbContextFactory = new TestDbContextFactory();
14+
DbContext = dbContextFactory.CreateDbContext(new string[] { });
15+
DbContext.Database.EnsureCreated();
16+
}
17+
18+
[TestCleanup]
19+
public void TearDown()
20+
{
21+
if (DbContext is not null)
22+
{
23+
DbContext.Database.EnsureDeleted();
24+
DbContext.Dispose();
25+
DbContext = null;
26+
}
27+
}
28+
}
29+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.1</TargetFramework>
5+
<RootNamespace>NBS.EntityFrameworkCore.SqlServer.Tests</RootNamespace>
6+
<IsPackable>false</IsPackable>
7+
<LangVersion>9.0</LangVersion>
8+
<Nullable>enable</Nullable>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
13+
<PackageReference Include="MSTest.TestAdapter" Version="2.1.0" />
14+
<PackageReference Include="MSTest.TestFramework" Version="2.1.0" />
15+
<PackageReference Include="coverlet.collector" Version="1.2.0" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<ProjectReference Include="..\NBS.EntityFrameworkCore.SqlServer.TryParse\NBS.EntityFrameworkCore.SqlServer.TryParse.csproj" />
20+
</ItemGroup>
21+
22+
</Project>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using Microsoft.EntityFrameworkCore;
3+
4+
namespace NBS.EntityFrameworkCore.SqlServer.Tests
5+
{
6+
public class TestDbContext : DbContext
7+
{
8+
public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
9+
{
10+
}
11+
12+
public DbSet<TestEntity> TestEntities => Set<TestEntity>();
13+
14+
/// <inheritdoc />
15+
protected override void OnModelCreating(ModelBuilder modelBuilder)
16+
{
17+
base.OnModelCreating(modelBuilder);
18+
TryParse.Register(modelBuilder);
19+
20+
var entity = modelBuilder.Entity<TestEntity>();
21+
entity.Property(t => t.Id).ValueGeneratedOnAdd();
22+
23+
entity.HasData(
24+
new TestEntity(1, "1"),
25+
new TestEntity(2, "2"),
26+
new TestEntity(3, "3"),
27+
new TestEntity(4, "Should not parse"));
28+
}
29+
}
30+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using Microsoft.EntityFrameworkCore;
3+
using Microsoft.EntityFrameworkCore.Design;
4+
5+
namespace NBS.EntityFrameworkCore.SqlServer.Tests
6+
{
7+
public class TestDbContextFactory : IDesignTimeDbContextFactory<TestDbContext>
8+
{
9+
/// <inheritdoc />
10+
public TestDbContext CreateDbContext(string[] args)
11+
{
12+
var optionsBuilder = new DbContextOptionsBuilder<TestDbContext>();
13+
optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=NBS.EntityFrameworkCore.SqlServer.Tests;Trusted_Connection=True;");
14+
return new TestDbContext(optionsBuilder.Options);
15+
}
16+
}
17+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
3+
namespace NBS.EntityFrameworkCore.SqlServer.Tests
4+
{
5+
public class TestEntity
6+
{
7+
public TestEntity(int id, string value)
8+
{
9+
Id = id;
10+
Value = value;
11+
}
12+
13+
public int Id { get; private set; }
14+
public string Value { get; private set; }
15+
}
16+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System.Linq;
2+
using Microsoft.VisualStudio.TestTools.UnitTesting;
3+
4+
namespace NBS.EntityFrameworkCore.SqlServer.Tests
5+
{
6+
[TestClass]
7+
public class TryParseTests : IntegrationTestBase
8+
{
9+
[TestMethod]
10+
public void CanParseInt32()
11+
{
12+
var values = DbContext!.TestEntities.Select(e => new { e.Value, ValueInt32 = TryParse.Int32(e.Value) }).ToList();
13+
Assert.AreNotEqual(0, values.Count);
14+
15+
foreach (var entity in values)
16+
{
17+
int? expected = int.TryParse(entity.Value, out var x) ? x : null;
18+
Assert.AreEqual(expected, entity.ValueInt32, $"Failed to parse '{entity.Value}' as Int32");
19+
}
20+
}
21+
}
22+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.31613.86
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NBS.EntityFrameworkCore.SqlServer.TryParse", "NBS.EntityFrameworkCore.SqlServer.TryParse\NBS.EntityFrameworkCore.SqlServer.TryParse.csproj", "{4209CA51-7A84-4DBE-A0E4-1E19AC064C1D}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NBS.EntityFrameworkCore.SqlServer.TryParse.Tests", "NBS.EntityFrameworkCore.SqlServer.TryParse.Tests\NBS.EntityFrameworkCore.SqlServer.TryParse.Tests.csproj", "{4587B026-DCE5-4BEE-833B-5A9BAA55D544}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Debug|Any CPU = Debug|Any CPU
13+
Release|Any CPU = Release|Any CPU
14+
EndGlobalSection
15+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
16+
{4209CA51-7A84-4DBE-A0E4-1E19AC064C1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17+
{4209CA51-7A84-4DBE-A0E4-1E19AC064C1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
18+
{4209CA51-7A84-4DBE-A0E4-1E19AC064C1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
19+
{4209CA51-7A84-4DBE-A0E4-1E19AC064C1D}.Release|Any CPU.Build.0 = Release|Any CPU
20+
{4587B026-DCE5-4BEE-833B-5A9BAA55D544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{4587B026-DCE5-4BEE-833B-5A9BAA55D544}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{4587B026-DCE5-4BEE-833B-5A9BAA55D544}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{4587B026-DCE5-4BEE-833B-5A9BAA55D544}.Release|Any CPU.Build.0 = Release|Any CPU
24+
EndGlobalSection
25+
GlobalSection(SolutionProperties) = preSolution
26+
HideSolutionNode = FALSE
27+
EndGlobalSection
28+
GlobalSection(ExtensibilityGlobals) = postSolution
29+
SolutionGuid = {FBB27A70-5830-49FC-912D-F40D72EDE149}
30+
EndGlobalSection
31+
EndGlobal
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<LangVersion>9.0</LangVersion>
6+
<Nullable>enable</Nullable>
7+
<RootNamespace>NBS.EntityFrameworkCore.SqlServer</RootNamespace>
8+
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\NBS.EntityFrameworkCore.SqlServer.TryParse.xml</DocumentationFile>
9+
</PropertyGroup>
10+
11+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
12+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
13+
<WarningsAsErrors />
14+
</PropertyGroup>
15+
16+
<PropertyGroup>
17+
<Product>NBS.EntityFrameworkCore.SqlServer</Product>
18+
<Company>Nevalee Business Solutions</Company>
19+
<Authors>Nevalee Business Solutions</Authors>
20+
<Copyright>Copyright © Nevalee Business Solutions</Copyright>
21+
<NeutralLanguage>en-GB</NeutralLanguage>
22+
<Description>EF Core methods to call SQL Server's TRY_PARSE function.</Description>
23+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
24+
<Version>1.0.0</Version>
25+
<RepositoryUrl>https://github.com/RichardD2/NBS.EntityFrameworkCore.SqlServer.TryParse</RepositoryUrl>
26+
<RepositoryType>GitHub</RepositoryType>
27+
<PackageProjectUrl>https://github.com/RichardD2/NBS.EntityFrameworkCore.SqlServer.TryParse</PackageProjectUrl>
28+
<PackageTags>Entity Framework Core</PackageTags>
29+
</PropertyGroup>
30+
31+
<ItemGroup>
32+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.*" />
33+
</ItemGroup>
34+
35+
</Project>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
3+
namespace NBS.EntityFrameworkCore.SqlServer
4+
{
5+
[AttributeUsage(AttributeTargets.Method)]
6+
internal sealed class SqlTypeNameAttribute : Attribute
7+
{
8+
public SqlTypeNameAttribute(string sqlTypeName)
9+
{
10+
if (string.IsNullOrEmpty(sqlTypeName)) throw new ArgumentNullException(nameof(sqlTypeName));
11+
SqlTypeName = sqlTypeName;
12+
}
13+
14+
public string SqlTypeName { get; }
15+
}
16+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
using System;
2+
using System.Linq;
3+
using System.Reflection;
4+
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
6+
7+
namespace NBS.EntityFrameworkCore.SqlServer
8+
{
9+
/// <summary>
10+
/// Methods for calling <a href="https://docs.microsoft.com/en-us/sql/t-sql/functions/try-parse-transact-sql" target="_blank">TRY_PARSE</a> from EF Core.
11+
/// </summary>
12+
/// <example>
13+
/// <para>Register the extension in the <c>OnModelCreating</c> method:</para>
14+
/// <code>
15+
/// protected override void OnModelCreating(ModelBuilder modelBuilder)
16+
/// {
17+
/// base.OnModelCreating(modelBuilder);
18+
/// TryParse.Register(modelBuilder);
19+
/// }
20+
/// </code>
21+
/// <para>You can then call the functions as part of a query against this context:</para>
22+
/// <code>
23+
/// var results = context.SomeSet.Select(e =&gt; new { e.Id, e.Name, ValueInt32 = TryParse.Int32(e.Value) }).ToList();
24+
/// </code>
25+
/// <para>This will be translated into a suitable SQL query:</para>
26+
/// <code>
27+
/// SELECT Id, Name, TRY_PARSE(Value As int) As ValueInt32 FROM SomeSet
28+
/// </code>
29+
/// </example>
30+
public static class TryParse
31+
{
32+
/// <summary>
33+
/// Registers the <c>TRY_PARSE</c> functions.
34+
/// </summary>
35+
/// <param name="modelBuilder">
36+
/// The <see cref="ModelBuilder"/>.
37+
/// </param>
38+
/// <exception cref="ArgumentNullException">
39+
/// <paramref name="modelBuilder"/> is <see langword="null"/>.
40+
/// </exception>
41+
public static void Register(ModelBuilder modelBuilder)
42+
{
43+
if (modelBuilder is null) throw new ArgumentNullException(nameof(modelBuilder));
44+
45+
foreach (var dbFunc in typeof(TryParse).GetMethods(BindingFlags.Public | BindingFlags.Static))
46+
{
47+
var attribute = dbFunc.GetCustomAttribute<SqlTypeNameAttribute>();
48+
if (attribute is null) continue;
49+
50+
modelBuilder.HasDbFunction(dbFunc).HasTranslation(args =>
51+
{
52+
var newArgs = args.ToList();
53+
newArgs[0] = new TryParseArgumentExpression(dbFunc.ReturnType, newArgs[0], attribute.SqlTypeName);
54+
return SqlFunctionExpression.Create("TRY_PARSE", newArgs, dbFunc.ReturnType, null);
55+
});
56+
}
57+
}
58+
59+
/// <summary>
60+
/// Attempts to parse the string as a byte.
61+
/// </summary>
62+
/// <param name="value">The string value to parse.</param>
63+
/// <returns>The parsed value, if available; otherwise, <see langword="null"/></returns>
64+
[SqlTypeName("tinyint")]
65+
public static byte? Byte(string value) => byte.TryParse(value, out var result) ? result : null;
66+
67+
/// <summary>
68+
/// Attempts to parse the string as a 16-bit integer.
69+
/// </summary>
70+
/// <param name="value">The string value to parse.</param>
71+
/// <returns>The parsed value, if available; otherwise, <see langword="null"/></returns>
72+
[SqlTypeName("smallint")]
73+
public static short? Int16(string value) => short.TryParse(value, out var result) ? result : null;
74+
75+
/// <summary>
76+
/// Attempts to parse the string as a 32-bit integer.
77+
/// </summary>
78+
/// <param name="value">The string value to parse.</param>
79+
/// <returns>The parsed value, if available; otherwise, <see langword="null"/></returns>
80+
[SqlTypeName("int")]
81+
public static int? Int32(string value) => int.TryParse(value, out var result) ? result : null;
82+
83+
/// <summary>
84+
/// Attempts to parse the string as a 64-bit integer.
85+
/// </summary>
86+
/// <param name="value">The string value to parse.</param>
87+
/// <returns>The parsed value, if available; otherwise, <see langword="null"/></returns>
88+
[SqlTypeName("bigint")]
89+
public static long? Int64(string value) => long.TryParse(value, out var result) ? result : null;
90+
91+
/// <summary>
92+
/// Attempts to parse the string as a decimal.
93+
/// </summary>
94+
/// <param name="value">The string value to parse.</param>
95+
/// <returns>The parsed value, if available; otherwise, <see langword="null"/></returns>
96+
[SqlTypeName("decimal")]
97+
public static decimal? Decimal(string value) => decimal.TryParse(value, out var result) ? result : null;
98+
99+
/// <summary>
100+
/// Attempts to parse the string as a double-precision floating-point number.
101+
/// </summary>
102+
/// <param name="value">The string value to parse.</param>
103+
/// <returns>The parsed value, if available; otherwise, <see langword="null"/></returns>
104+
[SqlTypeName("float")]
105+
public static double? Double(string value) => double.TryParse(value, out var result) ? result : null;
106+
107+
/// <summary>
108+
/// Attempts to parse the string as a single-precision floating-point number.
109+
/// </summary>
110+
/// <param name="value">The string value to parse.</param>
111+
/// <returns>The parsed value, if available; otherwise, <see langword="null"/></returns>
112+
[SqlTypeName("real")]
113+
public static float? Single(string value) => float.TryParse(value, out var result) ? result : null;
114+
115+
/// <summary>
116+
/// Attempts to parse the string as a date.
117+
/// </summary>
118+
/// <param name="value">The string value to parse.</param>
119+
/// <returns>The parsed value, if available; otherwise, <see langword="null"/></returns>
120+
[SqlTypeName("date")]
121+
public static DateTime? Date(string value) => System.DateTime.TryParse(value, out var result) ? result : null;
122+
123+
/// <summary>
124+
/// Attempts to parse the string as a <see cref="System.DateTime"/>.
125+
/// </summary>
126+
/// <param name="value">The string value to parse.</param>
127+
/// <returns>The parsed value, if available; otherwise, <see langword="null"/></returns>
128+
[SqlTypeName("datetime2")]
129+
public static DateTime? DateTime(string value) => System.DateTime.TryParse(value, out var result) ? result : null;
130+
131+
/// <summary>
132+
/// Attempts to parse the string as a <see cref="System.DateTimeOffset"/>.
133+
/// </summary>
134+
/// <param name="value">The string value to parse.</param>
135+
/// <returns>The parsed value, if available; otherwise, <see langword="null"/></returns>
136+
[SqlTypeName("datetimeoffset")]
137+
public static DateTimeOffset? DateTimeOffset(string value) => System.DateTimeOffset.TryParse(value, out var result) ? result : null;
138+
139+
/// <summary>
140+
/// Attempts to parse the string as a time.
141+
/// </summary>
142+
/// <param name="value">The string value to parse.</param>
143+
/// <returns>The parsed value, if available; otherwise, <see langword="null"/></returns>
144+
[SqlTypeName("time")]
145+
public static TimeSpan? Time(string value) => TimeSpan.TryParse(value, out var result) ? result : null;
146+
}
147+
}

0 commit comments

Comments
 (0)