Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scaffold with nullable reference types #24874

Merged
merged 2 commits into from
May 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/EFCore.Design/Design/Internal/DatabaseOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public virtual SavedModelFiles ScaffoldContext(
ModelNamespace = finalModelNamespace,
ContextNamespace = finalContextNamespace,
Language = _language,
Nullable = _nullable,
UseNullableReferenceTypes = _nullable,
ContextDir = MakeDirRelative(outputDir, outputContextDir),
ContextName = dbContextClassName,
SuppressOnConfiguring = suppressOnConfiguring
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public class CSharpDbContextGenerator : ICSharpDbContextGenerator
private readonly IAnnotationCodeGenerator _annotationCodeGenerator;
private IndentedStringBuilder _sb = null!;
private bool _entityTypeBuilderInitialized;
private bool _useDataAnnotations;
private bool _useNullableReferenceTypes;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -64,11 +66,15 @@ public virtual string WriteCode(
string? contextNamespace,
string? modelNamespace,
bool useDataAnnotations,
bool useNullableReferenceTypes,
bool suppressConnectionStringWarning,
bool suppressOnConfiguring)
{
Check.NotNull(model, nameof(model));

_useDataAnnotations = useDataAnnotations;
_useNullableReferenceTypes = useNullableReferenceTypes;

_sb = new IndentedStringBuilder();

_sb.AppendLine("using System;"); // Guid default values require new Guid() which requires this using
Expand All @@ -84,9 +90,6 @@ public virtual string WriteCode(

_sb.AppendLine();

_sb.AppendLine("#nullable disable");
_sb.AppendLine();

if (!string.IsNullOrEmpty(finalContextNamespace))
{
_sb.AppendLine($"namespace {finalContextNamespace}");
Expand All @@ -98,7 +101,6 @@ public virtual string WriteCode(
model,
contextName,
connectionString,
useDataAnnotations,
suppressConnectionStringWarning,
suppressOnConfiguring);

Expand All @@ -121,7 +123,6 @@ protected virtual void GenerateClass(
IModel model,
string contextName,
string connectionString,
bool useDataAnnotations,
bool suppressConnectionStringWarning,
bool suppressOnConfiguring)
{
Expand All @@ -142,7 +143,7 @@ protected virtual void GenerateClass(
GenerateOnConfiguring(connectionString, suppressConnectionStringWarning);
}

GenerateOnModelCreating(model, useDataAnnotations);
GenerateOnModelCreating(model);
}

_sb.AppendLine();
Expand Down Expand Up @@ -175,8 +176,14 @@ private void GenerateDbSets(IModel model)
{
foreach (var entityType in model.GetEntityTypes())
{
_sb.AppendLine(
$"public virtual DbSet<{entityType.Name}> {entityType.GetDbSetName()} {{ get; set; }}");
_sb.Append($"public virtual DbSet<{entityType.Name}> {entityType.GetDbSetName()} {{ get; set; }}");

if (_useNullableReferenceTypes)
{
_sb.Append(" = null!;");
}

_sb.AppendLine();
}

if (model.GetEntityTypes().Any())
Expand Down Expand Up @@ -258,9 +265,7 @@ protected virtual void GenerateOnConfiguring(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected virtual void GenerateOnModelCreating(
IModel model,
bool useDataAnnotations)
protected virtual void GenerateOnModelCreating(IModel model)
{
Check.NotNull(model, nameof(model));

Expand Down Expand Up @@ -310,7 +315,7 @@ protected virtual void GenerateOnModelCreating(
{
_entityTypeBuilderInitialized = false;

GenerateEntityType(entityType, useDataAnnotations);
GenerateEntityType(entityType);

if (_entityTypeBuilderInitialized)
{
Expand Down Expand Up @@ -346,9 +351,9 @@ private void InitializeEntityTypeBuilder(IEntityType entityType)
_entityTypeBuilderInitialized = true;
}

private void GenerateEntityType(IEntityType entityType, bool useDataAnnotations)
private void GenerateEntityType(IEntityType entityType)
{
GenerateKey(entityType.FindPrimaryKey(), entityType, useDataAnnotations);
GenerateKey(entityType.FindPrimaryKey(), entityType);

var annotations = _annotationCodeGenerator
.FilterIgnoredAnnotations(entityType.GetAnnotations())
Expand All @@ -362,14 +367,14 @@ private void GenerateEntityType(IEntityType entityType, bool useDataAnnotations)
annotations.Remove(ScaffoldingAnnotationNames.DbSetName);
annotations.Remove(RelationalAnnotationNames.ViewDefinitionSql);

if (useDataAnnotations)
if (_useDataAnnotations)
{
// Strip out any annotations handled as attributes - these are already handled when generating
// the entity's properties
_ = _annotationCodeGenerator.GenerateDataAnnotationAttributes(entityType, annotations);
}

if (!useDataAnnotations || entityType.GetViewName() != null)
if (!_useDataAnnotations || entityType.GetViewName() != null)
{
GenerateTableName(entityType);
}
Expand All @@ -389,20 +394,20 @@ private void GenerateEntityType(IEntityType entityType, bool useDataAnnotations)
.ToDictionary(a => a.Name, a => a);
_annotationCodeGenerator.RemoveAnnotationsHandledByConventions(index, indexAnnotations);

if (!useDataAnnotations || indexAnnotations.Count > 0)
if (!_useDataAnnotations || indexAnnotations.Count > 0)
{
GenerateIndex(index);
}
}

foreach (var property in entityType.GetProperties())
{
GenerateProperty(property, useDataAnnotations);
GenerateProperty(property);
}

foreach (var foreignKey in entityType.GetForeignKeys())
{
GenerateRelationship(foreignKey, useDataAnnotations);
GenerateRelationship(foreignKey);
}
}

Expand Down Expand Up @@ -434,11 +439,11 @@ private void AppendMultiLineFluentApi(IEntityType entityType, IList<string> line
}
}

private void GenerateKey(IKey? key, IEntityType entityType, bool useDataAnnotations)
private void GenerateKey(IKey? key, IEntityType entityType)
{
if (key == null)
{
if (!useDataAnnotations)
if (!_useDataAnnotations)
{
var line = new List<string> { $".{nameof(EntityTypeBuilder.HasNoKey)}()" };

Expand Down Expand Up @@ -469,7 +474,7 @@ private void GenerateKey(IKey? key, IEntityType entityType, bool useDataAnnotati
}

if (!explicitName
&& useDataAnnotations)
&& _useDataAnnotations)
{
return;
}
Expand Down Expand Up @@ -557,7 +562,7 @@ private void GenerateIndex(IIndex index)
AppendMultiLineFluentApi(index.DeclaringEntityType, lines);
}

private void GenerateProperty(IProperty property, bool useDataAnnotations)
private void GenerateProperty(IProperty property)
{
var lines = new List<string> { $".{nameof(EntityTypeBuilder.Property)}({_code.Lambda(new[] { property.Name }, "e")})" };

Expand All @@ -567,7 +572,7 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations)
_annotationCodeGenerator.RemoveAnnotationsHandledByConventions(property, annotations);
annotations.Remove(ScaffoldingAnnotationNames.ColumnOrdinal);

if (useDataAnnotations)
if (_useDataAnnotations)
{
// Strip out any annotations handled as attributes - these are already handled when generating
// the entity's properties
Expand All @@ -579,7 +584,8 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations)
}
else
{
if (!property.IsNullable
if ((!_useNullableReferenceTypes || property.ClrType.IsValueType)
&& !property.IsNullable
&& property.ClrType.IsNullableType()
&& !property.IsPrimaryKey())
{
Expand Down Expand Up @@ -679,7 +685,7 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations)
AppendMultiLineFluentApi(property.DeclaringEntityType, lines);
}

private void GenerateRelationship(IForeignKey foreignKey, bool useDataAnnotations)
private void GenerateRelationship(IForeignKey foreignKey)
{
var canUseDataAnnotations = true;
var annotations = _annotationCodeGenerator
Expand Down Expand Up @@ -732,7 +738,7 @@ private void GenerateRelationship(IForeignKey foreignKey, bool useDataAnnotation
_annotationCodeGenerator.GenerateFluentApiCalls(foreignKey, annotations).Select(m => _code.Fragment(m))
.Concat(GenerateAnnotations(annotations.Values)));

if (!useDataAnnotations
if (!_useDataAnnotations
|| !canUseDataAnnotations)
{
AppendMultiLineFluentApi(foreignKey.DeclaringEntityType, lines);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Globalization;
using System.Linq;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Design.Internal;
Expand All @@ -28,6 +29,7 @@ public class CSharpEntityTypeGenerator : ICSharpEntityTypeGenerator

private IndentedStringBuilder _sb = null!;
private bool _useDataAnnotations;
private bool _useNullableReferenceTypes;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -51,12 +53,14 @@ public CSharpEntityTypeGenerator(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual string WriteCode(IEntityType entityType, string? @namespace, bool useDataAnnotations)
public virtual string WriteCode(IEntityType entityType, string? @namespace, bool useDataAnnotations, bool useNullableReferenceTypes)
{
Check.NotNull(entityType, nameof(entityType));

_sb = new IndentedStringBuilder();
_useDataAnnotations = useDataAnnotations;
_useNullableReferenceTypes = useNullableReferenceTypes;

_sb = new IndentedStringBuilder();

_sb.AppendLine("using System;");
_sb.AppendLine("using System.Collections.Generic;");
Expand All @@ -77,9 +81,6 @@ public virtual string WriteCode(IEntityType entityType, string? @namespace, bool
_sb.AppendLine($"using {ns};");
}

_sb.AppendLine();
_sb.AppendLine("#nullable disable");

_sb.AppendLine();

if (!string.IsNullOrEmpty(@namespace))
Expand Down Expand Up @@ -277,7 +278,12 @@ protected virtual void GenerateProperties(IEntityType entityType)
GeneratePropertyDataAnnotations(property);
}

_sb.AppendLine($"public {_code.Reference(property.ClrType)} {property.Name} {{ get; set; }}");
_sb.AppendLine(
!_useNullableReferenceTypes || property.ClrType.IsValueType
? $"public {_code.Reference(property.ClrType)} {property.Name} {{ get; set; }}"
: property.IsNullable
? $"public {_code.Reference(property.ClrType)}? {property.Name} {{ get; set; }}"
: $"public {_code.Reference(property.ClrType)} {property.Name} {{ get; set; }} = null!;");
}
}

Expand Down Expand Up @@ -350,7 +356,8 @@ private void GenerateColumnAttribute(IProperty property)

private void GenerateRequiredAttribute(IProperty property)
{
if (!property.IsNullable
if ((!_useNullableReferenceTypes || property.ClrType.IsValueType)
&& !property.IsNullable
&& property.ClrType.IsNullableType()
&& !property.IsPrimaryKey())
{
Expand Down Expand Up @@ -440,7 +447,13 @@ protected virtual void GenerateNavigationProperties(IEntityType entityType)

var referencedTypeName = navigation.TargetEntityType.Name;
var navigationType = navigation.IsCollection ? $"ICollection<{referencedTypeName}>" : referencedTypeName;
_sb.AppendLine($"public virtual {navigationType} {navigation.Name} {{ get; set; }}");

_sb.AppendLine(
!_useNullableReferenceTypes || navigation.IsCollection
? $"public virtual {navigationType} {navigation.Name} {{ get; set; }}"
: navigation.ForeignKey.IsRequired
? $"public virtual {navigationType} {navigation.Name} {{ get; set; }} = null!;"
: $"public virtual {navigationType}? {navigation.Name} {{ get; set; }}");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ public override ScaffoldedModel GenerateModel(
CoreStrings.ArgumentPropertyNull(nameof(options.ConnectionString), nameof(options)), nameof(options));
}

// TODO: Honor options.Nullable (issue #15520)
var generatedCode = CSharpDbContextGenerator.WriteCode(
model,
options.ContextName,
options.ConnectionString,
options.ContextNamespace,
options.ModelNamespace,
options.UseDataAnnotations,
options.UseNullableReferenceTypes,
options.SuppressConnectionStringWarning,
options.SuppressOnConfiguring);

Expand All @@ -114,8 +114,11 @@ public override ScaffoldedModel GenerateModel(

foreach (var entityType in model.GetEntityTypes())
{
// TODO: Honor options.Nullable (issue #15520)
generatedCode = CSharpEntityTypeGenerator.WriteCode(entityType, options.ModelNamespace, options.UseDataAnnotations);
generatedCode = CSharpEntityTypeGenerator.WriteCode(
entityType,
options.ModelNamespace,
options.UseDataAnnotations,
options.UseNullableReferenceTypes);

// output EntityType poco .cs file
var entityTypeFileName = entityType.Name + FileExtension;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ string WriteCode(
string? contextNamespace,
string? modelNamespace,
bool useDataAnnotations,
bool useNullableReferenceTypes,
bool suppressConnectionStringWarning,
bool suppressOnConfiguring);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ public interface ICSharpEntityTypeGenerator
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
string WriteCode(IEntityType entityType, string? @namespace, bool useDataAnnotations);
string WriteCode(IEntityType entityType, string? @namespace, bool useDataAnnotations, bool useNullableReferenceTypes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public class ModelCodeGenerationOptions
/// Gets or sets a value indicating whether nullable reference types are enabled.
/// </summary>
/// <value> A value indicating whether nullable reference types are enabled. </value>
public virtual bool Nullable { get; set; }
public virtual bool UseNullableReferenceTypes { get; set; }

/// <summary>
/// Gets or sets the DbContext output directory.
Expand Down
Loading