diff --git a/src/Abstractions/ISoftDeletable.cs b/src/Abstractions/ISoftDeletable.cs
index 42f28a9..e647705 100644
--- a/src/Abstractions/ISoftDeletable.cs
+++ b/src/Abstractions/ISoftDeletable.cs
@@ -3,5 +3,9 @@ namespace Dgmjr.EntityFrameworkCore.Abstractions;
public interface ISoftDeletable
{
DateTimeOffset? Deleted { get; set; }
- bool IsDeleted { get; }
+#if NET6_0_OR_GREATER
+ bool IsDeleted => Deleted is not null && Deleted.Value < DateTimeOffset.Now;
+#else
+ bool IsDeleted { get; set; }
+#endif
}
diff --git a/src/Constants/Constants/Constants.cs b/src/Constants/Constants/Constants.cs
index 4939303..1d5655f 100644
--- a/src/Constants/Constants/Constants.cs
+++ b/src/Constants/Constants/Constants.cs
@@ -11,7 +11,7 @@ public static class Prefixes
/// ufn_
public const string ufn_ = nameof(ufn_);
- /// sp_
+ /// udp_
public const string udp_ = nameof(udp_);
/// sp_
diff --git a/src/Constants/DbTypesNamesAndSchemas/DbTypeNameEnum.cs b/src/Constants/DbTypesNamesAndSchemas/DbTypeNameEnum.cs
index 731a4c9..7a7cff8 100644
--- a/src/Constants/DbTypesNamesAndSchemas/DbTypeNameEnum.cs
+++ b/src/Constants/DbTypesNamesAndSchemas/DbTypeNameEnum.cs
@@ -79,6 +79,9 @@ public enum DbTypeNames
/// Database type of datetime.
[Display(Name = "datetime", Description = "Database type of datetime.", ShortName = "datetime")]
SqlDateTime,
+ /// Database type of datetime2.
+ [Display(Name = "datetime2", Description = "Database type of datetime2.", ShortName = "datetime")]
+ SqlDateTime2,
/// Database type of date.
[Display(Name = "date", Description = "Database type of date.", ShortName = "date")]
@@ -122,5 +125,41 @@ public enum DbTypeNames
Description = "Database type of uniqueidentifier.",
ShortName = "uniqueidentifier"
)]
- UniqueIdentifier
+ UniqueIdentifier,
+
+ /// Database type of float.
+ [Display(
+ Name = "float",
+ Description = "Database type of float.",
+ ShortName = "float"
+ )]
+ Float,
+ /// Database type of GEOMETRY.
+ [Display(
+ Name = "GEOMETRY",
+ Description = "Database type of GEOMETRY.",
+ ShortName = "GEOMETRY"
+ )]
+ Geometry,
+ /// Database type of GEOGRAPHY.
+ [Display(
+ Name = "GEOGRAPHY",
+ Description = "Database type of GEOGRAPHY.",
+ ShortName = "GEOGRAPHY"
+ )]
+ Geography,
+ /// Database type of VARCHAR({0}).
+ [Display(
+ Name = "VARCHAR({0})",
+ Description = "Database type of VARCHAR({0}).",
+ ShortName = "VARCHAR({0})"
+ )]
+ VarCharX,
+ /// Database type of NVARCHAR({0}).
+ [Display(
+ Name = "NVARCHAR({0})",
+ Description = "Database type of NVARCHAR({0}).",
+ ShortName = "NVARCHAR({0})"
+ )]
+ NVarCharX,
}
diff --git a/src/Constants/DbTypesNamesAndSchemas/Dgmjr.EntityFrameworkCore.Constants.DbTypesNamesAndSchemas.csproj b/src/Constants/DbTypesNamesAndSchemas/Dgmjr.EntityFrameworkCore.Constants.DbTypesNamesAndSchemas.csproj
index 4638456..2eeabca 100644
--- a/src/Constants/DbTypesNamesAndSchemas/Dgmjr.EntityFrameworkCore.Constants.DbTypesNamesAndSchemas.csproj
+++ b/src/Constants/DbTypesNamesAndSchemas/Dgmjr.EntityFrameworkCore.Constants.DbTypesNamesAndSchemas.csproj
@@ -1,6 +1,7 @@
netstandard2.0
+ Provides a set of constants and enums for EF Core
@@ -10,5 +11,6 @@
+
diff --git a/src/Constants/DbTypesNamesAndSchemas/SchemasEnum.cs b/src/Constants/DbTypesNamesAndSchemas/SchemasEnum.cs
index f44e0ef..3388f8d 100644
--- a/src/Constants/DbTypesNamesAndSchemas/SchemasEnum.cs
+++ b/src/Constants/DbTypesNamesAndSchemas/SchemasEnum.cs
@@ -13,7 +13,7 @@
namespace Dgmjr.EntityFrameworkCore.Enums;
[GenerateEnumerationRecordStruct("DbSchemas", "Dgmjr.EntityFrameworkCore")]
-public enum SchemasEnum
+public enum Schemas
{
[Display(Name = "DBO Schema", ShortName = "dbo", Description = "The dbo schema")]
DboSchema,
diff --git a/src/Migrations/Constants.cs b/src/Migrations/Constants.cs
index b67b5d2..99c0773 100644
--- a/src/Migrations/Constants.cs
+++ b/src/Migrations/Constants.cs
@@ -1,11 +1,44 @@
+using System.Domain;
+using System.Net.Mail;
+using System.Runtime.InteropServices.ComTypes;
+
+using Dgmjr.EntityFrameworkCore.Constants;
+
+using Byte = Dgmjr.EntityFrameworkCore.DbTypeNames.Byte;
+
namespace Dgmjr.EntityFrameworkCore.Migrations;
public static class Constants
{
- public const string CreateOrAlterFunctionPattern =
- "CREATE OR ALTER FUNCTION {0}.{1}({2}) RETURNS BIT AS BEGIN {3} END";
+ public const string CreateOrAlterFunctionPattern = """
+ CREATE OR ALTER FUNCTION {0}.{1}({2})
+ RETURNS {3}
+ AS
+ BEGIN
+ {4}
+ END
+ """;
+ public const string CreateOrAlterViewPattern = """
+ CREATE OR ALTER VIEW {0}.{1}
+ AS
+ {2}
+ """;
+ public const string CreateOrAlterProcedurePattern = """
+ CREATE OR ALTER PROCEDURE {0}.{1} {2}
+ AS
+ BEGIN
+ {3}
+ END
+ """;
+
+ public const string Function = "FUNCTION";
+ public const string Procedure = "PROCEDURE";
+ public const string View = "VIEW";
public const string DropFunctionIfExistsPattern = "DROP FUNCTION IF EXISTS {0}.{1}";
+ public const string DropProcedureIfExistsPattern = "DROP PROCEDURE IF EXISTS {0}.{1}";
+ public const string DropViewIfExistsPattern = "DROP VIEW IF EXISTS {0}.{1}";
+ public const string DropSomethingIfExistsPattern = "DROP {0} IF EXISTS {1}.{2}";
public static readonly IReadOnlyDictionary ClrTypeToSqlTypeMap = new Dictionary<
Type,
@@ -15,7 +48,44 @@ public static class Constants
{ typeof(string), NVarCharMax.ShortName },
{ typeof(int), Int.ShortName },
{ typeof(long), BigInt.ShortName },
- { typeof(datetime), SqlDateTime.ShortName },
- { typeof(DateTimeOffset), SqlDateTimeOffset.ShortName }
+ { typeof(DateTimeOffset), SqlDateTimeOffset.ShortName },
+ { typeof(guid), UniqueIdentifier.ShortName },
+ { typeof(bool), Bit.ShortName },
+ { typeof(decimal), Float.ShortName },
+ { typeof(double), Float.ShortName },
+ { typeof(float), Float.ShortName },
+ { typeof(byte), Byte.ShortName },
+ { typeof(short), TinyInt.ShortName },
+ { typeof(byte[]), VarBinaryMax.ShortName },
+ { typeof(DateTime), Date.ShortName },
+ { typeof(DateTime?), Date.ShortName },
+ { typeof(DateTimeOffset?), SqlDateTime2.ShortName },
+ { typeof(TimeSpan), Time.ShortName },
+ { typeof(TimeSpan?), Time.ShortName },
+ { typeof(decimal?), Float.ShortName },
+ { typeof(double?), Float.ShortName },
+ { typeof(float?), Float.ShortName },
+ { typeof(byte?), Byte.ShortName },
+ { typeof(short?), TinyInt.ShortName },
+ { typeof(int?), Int.ShortName },
+ { typeof(long?), BigInt.ShortName },
+ { typeof(bool?), Bit.ShortName },
+ { typeof(NetTopologySuite.Geometries.Point), Geography.ShortName },
+ { typeof(NetTopologySuite.Geometries.LineString), Geography.ShortName },
+ { typeof(NetTopologySuite.Geometries.Polygon), Geography.ShortName },
+ { typeof(NetTopologySuite.Geometries.MultiPoint), Geography.ShortName },
+ { typeof(NetTopologySuite.Geometries.MultiLineString), Geography.ShortName },
+ { typeof(NetTopologySuite.Geometries.MultiPolygon), Geography.ShortName },
+ { typeof(NetTopologySuite.Geometries.GeometryCollection), Geography.ShortName },
+ { typeof(Slug), Format(CharX.DisplayName, 6) },
+ { typeof(Uri), NVarCharMax.DisplayName },
+ { typeof(uri), NVarCharMax.DisplayName },
+ { typeof(urn), NVarCharMax.DisplayName },
+ { typeof(url), NVarCharMax.DisplayName },
+ { typeof(xri), NVarCharMax.DisplayName },
+ { typeof(EmailAddress), Format(NVarCharX.DisplayName, UriMaxLengthConstant.UriMaxLength) },
+ { typeof(PhoneNumber), Format(VarCharX.DisplayName, 24) },
+ { typeof(EmailAddress?), Format(NVarCharX.DisplayName, UriMaxLengthConstant.UriMaxLength) },
+ { typeof(PhoneNumber?), Format(VarCharX.DisplayName, 24) },
};
}
diff --git a/src/Migrations/CreateFunctionOperation.cs b/src/Migrations/CreateFunctionOperation.cs
index 35de21f..7f9e5c1 100644
--- a/src/Migrations/CreateFunctionOperation.cs
+++ b/src/Migrations/CreateFunctionOperation.cs
@@ -2,31 +2,34 @@ namespace Dgmjr.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
-public class CreateFunctionOperation(string schema, string name, string arguments, string body)
+public class CreateFunctionOperation(string schema, string name, SqlArgument[] arguments, string returnType, string body)
: SqlOperation
{
public string Schema { get; set; } = schema;
public string Name { get; set; } = name;
- public string Arguments { get; set; } = arguments;
+ public SqlArgument[] Arguments { get; set; } = arguments;
public string Body { get; set; } = body;
+ public string ReturnType { get; set; } = returnType;
public override string Sql =>
- Format(CreateOrAlterFunctionPattern, Schema, Name, Arguments, Body);
+ Format(CreateOrAlterFunctionPattern, Schema, Name, Arguments.Join(", "), ReturnType, Body);
public CreateFunctionOperation(
MethodInfo mi,
string body,
string schema = DboSchema.ShortName,
string? name = default,
- string? arguments = default
+ SqlArgument[]? arguments = default
)
: this(
schema,
name ?? mi.Name,
arguments
?? mi.GetParameters()
- .Select(p => $"@{p.Name} {ClrTypeToSqlTypeMap[p.ParameterType]}")
- .Join(", "),
+ // .Select(p => $"@{p.Name} {ClrTypeToSqlTypeMap[p.ParameterType]}").ToArray(),
+ .Select(arg => (SqlArgument)arg)
+ .ToArray(),
+ ClrTypeToSqlTypeMap[mi.ReturnType],
body
) { }
}
diff --git a/src/Migrations/CreateProcedureOperation.cs b/src/Migrations/CreateProcedureOperation.cs
new file mode 100644
index 0000000..cd94493
--- /dev/null
+++ b/src/Migrations/CreateProcedureOperation.cs
@@ -0,0 +1,32 @@
+namespace Dgmjr.EntityFrameworkCore.Migrations;
+
+using Microsoft.EntityFrameworkCore.Migrations.Operations;
+
+public class CreateProcedureOperation(string schema, string name, SqlArgument[] arguments, string body)
+ : SqlOperation
+{
+ public string Schema { get; set; } = schema;
+ public string Name { get; set; } = name;
+ public SqlArgument[] Arguments { get; set; } = arguments;
+ public string Body { get; set; } = body;
+
+ public override string Sql =>
+ Format(CreateOrAlterProcedurePattern, Schema, Name, Arguments.Join(", "), Body);
+
+ public CreateProcedureOperation(
+ MethodInfo mi,
+ string body,
+ string schema = DboSchema.ShortName,
+ string? name = default,
+ SqlArgument[]? arguments = default
+ )
+ : this(
+ schema,
+ name ?? mi.Name,
+ arguments
+ ?? mi.GetParameters()
+ .Select(arg => (SqlArgument)arg)
+ .ToArray(),
+ body
+ ) { }
+}
diff --git a/src/Migrations/CreateViewOperation.cs b/src/Migrations/CreateViewOperation.cs
index c5f8bbb..2e5c9d8 100644
--- a/src/Migrations/CreateViewOperation.cs
+++ b/src/Migrations/CreateViewOperation.cs
@@ -1,11 +1,10 @@
-// namespace Dgmjr.EntityFrameworkCore.Migrations;
+namespace Dgmjr.EntityFrameworkCore.Migrations;
-// public class CreateViewOperation(string schema, string name, params Column[] columns) : SqlOperation
-// {
-// public string Schema { get; set; } = schema;
-// public string Name { get; set; } = name;
-// public Column[] Columns { get; set; } = columns;
-// public Expression
-// }
+public class CreateViewOperation(string schema, string name, string selectStatement) : SqlOperation
+{
+ public string Schema { get; set; } = schema;
+ public string Name { get; set; } = name;
+ public string SelectStatement { get; set; } = selectStatement;
-// public record struct Column(string DataType, string SourceTable, string ColumnName);
+ public override string Sql => Format(CreateOrAlterViewPattern, Schema, Name, SelectStatement);
+}
diff --git a/src/Migrations/CustomSqlMigrationsGenerator.cs b/src/Migrations/CustomSqlMigrationsGenerator.cs
index a0906ad..9083d5f 100644
--- a/src/Migrations/CustomSqlMigrationsGenerator.cs
+++ b/src/Migrations/CustomSqlMigrationsGenerator.cs
@@ -29,7 +29,7 @@ ICommandBatchPreparer commandBatchPreparer
protected override void Generate(
MigrationOperation operation,
- IModel model,
+ IModel? model,
MigrationCommandListBuilder builder
)
{
@@ -41,6 +41,22 @@ MigrationCommandListBuilder builder
{
builder.Append(dfo.Sql).Append(SqlHelper.StatementTerminator).EndCommand();
}
+ else if (operation is DropViewOperation dvo)
+ {
+ builder.Append(dvo.Sql).Append(SqlHelper.StatementTerminator).EndCommand();
+ }
+ else if (operation is CreateViewOperation cvo)
+ {
+ builder.Append(cvo.Sql).Append(SqlHelper.StatementTerminator).EndCommand();
+ }
+ else if (operation is DropOperation dropop)
+ {
+ builder.Append(dropop.Sql).Append(SqlHelper.StatementTerminator).EndCommand();
+ }
+ else if (operation is CreateProcedureOperation cpop)
+ {
+ builder.Append(cpop.Sql).Append(SqlHelper.StatementTerminator).EndCommand();
+ }
else
{
base.Generate(operation, model, builder);
diff --git a/src/Migrations/Dgmjr.EntityFrameworkCore.Migrations.csproj b/src/Migrations/Dgmjr.EntityFrameworkCore.Migrations.csproj
index fde521a..21f16b6 100644
--- a/src/Migrations/Dgmjr.EntityFrameworkCore.Migrations.csproj
+++ b/src/Migrations/Dgmjr.EntityFrameworkCore.Migrations.csproj
@@ -1,12 +1,15 @@
netstandard2.0;netstandard2.1;net6.0;net8.0
+ Entity Framework Core Migrations
-
-
+
+
+
+
diff --git a/src/Migrations/DropFunctionOperation.cs b/src/Migrations/DropFunctionOperation.cs
index 5e76bbf..13e48cb 100644
--- a/src/Migrations/DropFunctionOperation.cs
+++ b/src/Migrations/DropFunctionOperation.cs
@@ -2,11 +2,6 @@ namespace Dgmjr.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
-public class DropFunctionOperation(string schema, string name) : SqlOperation
+public class DropFunctionOperation(string schema, string name) : DropOperation(Function, schema, name)
{
- public string Schema { get; set; } = schema;
- public string Name { get; set; } = name;
- public override bool IsDestructiveChange => true;
-
- public override string Sql => Format(DropFunctionIfExistsPattern, Schema, Name);
}
diff --git a/src/Migrations/DropOperation.cs b/src/Migrations/DropOperation.cs
new file mode 100644
index 0000000..13630f3
--- /dev/null
+++ b/src/Migrations/DropOperation.cs
@@ -0,0 +1,11 @@
+namespace Dgmjr.EntityFrameworkCore.Migrations;
+
+public class DropOperation(string thingKind, string schema, string name) : SqlOperation
+{
+ public string ThingKind { get; set; } = thingKind;
+ public string Schema { get; set; } = schema;
+ public string Name { get; set; } = name;
+ public override bool IsDestructiveChange => true;
+
+ public override string Sql => Format(DropSomethingIfExistsPattern, ThingKind, Schema, Name);
+}
diff --git a/src/Migrations/DropProcedureOperation.cs b/src/Migrations/DropProcedureOperation.cs
new file mode 100644
index 0000000..ae39fb3
--- /dev/null
+++ b/src/Migrations/DropProcedureOperation.cs
@@ -0,0 +1,8 @@
+namespace Dgmjr.EntityFrameworkCore.Migrations;
+
+using Microsoft.EntityFrameworkCore.Migrations.Operations;
+
+public class DropProcedureOperation(string schema, string name) : DropOperation(Procedure, schema, name)
+{
+
+}
diff --git a/src/Migrations/DropViewOperation.cs b/src/Migrations/DropViewOperation.cs
new file mode 100644
index 0000000..36d9d63
--- /dev/null
+++ b/src/Migrations/DropViewOperation.cs
@@ -0,0 +1,7 @@
+namespace Dgmjr.EntityFrameworkCore.Migrations;
+
+using Microsoft.EntityFrameworkCore.Migrations.Operations;
+
+public class DropViewOperation(string schema, string name) : DropOperation(View, schema, name)
+{
+}
diff --git a/src/Migrations/MigrationBuilderExtensions.cs b/src/Migrations/MigrationBuilderExtensions.cs
new file mode 100644
index 0000000..838f0d1
--- /dev/null
+++ b/src/Migrations/MigrationBuilderExtensions.cs
@@ -0,0 +1,41 @@
+namespace Dgmjr.EntityFrameworkCore.Migrations;
+
+public static class MigrationBuilderExtensions
+{
+ public static void CreateView(this MigrationBuilder migrationBuilder, string schema, string name, string selectStatement)
+ {
+ migrationBuilder.Operations.Add(new CreateViewOperation(schema, name, selectStatement));
+ }
+
+ public static void DropView(this MigrationBuilder migrationBuilder, string schema, string name)
+ {
+ migrationBuilder.Operations.Add(new DropViewOperation(schema, name));
+ }
+
+ public static void DropProcedure(this MigrationBuilder migrationBuilder, string schema, string name)
+ {
+ migrationBuilder.Operations.Add(new DropProcedureOperation(schema, name));
+ }
+
+ public static void DropFunction(this MigrationBuilder migrationBuilder, string schema, string name)
+ {
+ migrationBuilder.Operations.Add(new DropFunctionOperation(schema, name));
+ }
+
+ public static void CreateProcedure(this MigrationBuilder migrationBuilder, string schema, string name, string[] args, string sql)
+ {
+ migrationBuilder.Operations.Add(new CreateProcedureOperation(schema, name, args.Select(arg => (SqlArgument)arg).ToArray(), sql));
+ }
+ public static void CreateProcedure(this MigrationBuilder migrationBuilder, MethodInfo mi, string schema, string name, string sql)
+ {
+ migrationBuilder.Operations.Add(new CreateProcedureOperation(mi, sql, schema, name));
+ }
+ public static void CreateFunction(this MigrationBuilder migrationBuilder, string schema, string name, string[] args, string returnType, string sql)
+ {
+ migrationBuilder.Operations.Add(new CreateFunctionOperation(schema, name, args.Select(arg => (SqlArgument)arg).ToArray(), returnType, sql));
+ }
+ public static void CreateFunction(this MigrationBuilder migrationBuilder, MethodInfo mi, string schema, string name, string sql)
+ {
+ migrationBuilder.Operations.Add(new CreateFunctionOperation(mi, sql, schema, name));
+ }
+}
diff --git a/src/Migrations/SqlArgument.cs b/src/Migrations/SqlArgument.cs
new file mode 100644
index 0000000..959871b
--- /dev/null
+++ b/src/Migrations/SqlArgument.cs
@@ -0,0 +1,34 @@
+using System.Runtime.InteropServices;
+
+namespace Dgmjr.EntityFrameworkCore.Migrations;
+
+[RegexDto(SqlArgumentRegex)]
+[StructLayout(LayoutKind.Auto)]
+public readonly partial struct SqlArgument
+{
+ private readonly string _name;
+ public string Name { get => _name; init => _name = value.StartsWith("@") ? value : "@" + value; }
+ public string Type { get; }
+ public string? Value { get; }
+
+ public override string ToString() => $"{Name} {Type}{(Value is not null ? $" = {Value}" : "")}";
+
+ public static implicit operator string(SqlArgument arg) => arg.ToString();
+
+ public SqlArgument(string name, string type, string? value = null) =>
+ (Name, Type, Value) = (name, type, value);
+
+ public static implicit operator SqlArgument((string name, string type, string? value) arg) =>
+ new(arg.name, arg.type, arg.value);
+
+ public static implicit operator SqlArgument((string name, string type) arg) =>
+ new(arg.name, arg.type);
+
+ public static implicit operator SqlArgument(string arg) =>
+ new(arg, "");
+
+ public static implicit operator SqlArgument(ParameterInfo arg) =>
+ new(arg.Name, ClrTypeToSqlTypeMap[arg.ParameterType]);
+
+ private const string SqlArgumentRegex = @"(?@\w+)\s+(?\w+)(\s*=\s*(?.*))?";
+}
diff --git a/src/Models/Slug.cs b/src/Models/Slug.cs
index 91b7e14..30a157e 100644
--- a/src/Models/Slug.cs
+++ b/src/Models/Slug.cs
@@ -3,12 +3,12 @@ namespace Dgmjr.EntityFrameworkCore;
using Vogen;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
-public readonly partial struct Slug(string Value) : IComparable, IEquatable, IComparable
+public readonly partial struct Slug(string? Value = default) : IComparable, IEquatable, IComparable
{
public Slug()
: this(NewSlug()) { }
- public string Value { get; init; } = Value;
+ public string Value { get; init; } = Value ?? NewSlug();
public int CompareTo(Slug other)
=> Compare(Value, other.Value, OrdinalIgnoreCase);
@@ -28,11 +28,26 @@ public bool Equals(Slug other)
public override bool Equals(object? obj)
=> obj is Slug other && Equals(other);
+ public override int GetHashCode()
+ => Value.GetHashCode();
+
public static bool operator==(Slug left, Slug right)
=> left.Equals(right);
public static bool operator!=(Slug left, Slug right)
=> !left.Equals(right);
+
+ public static bool operator<(Slug left, Slug right)
+ => left.CompareTo(right) < 0;
+
+ public static bool operator<=(Slug left, Slug right)
+ => left.CompareTo(right) <= 0;
+
+ public static bool operator>(Slug left, Slug right)
+ => left.CompareTo(right) > 0;
+
+ public static bool operator>=(Slug left, Slug right)
+ => left.CompareTo(right) >= 0;
}
public class SlugEfCoreConverter : ValueConverter