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

Adding a BackingField attribute #19989

Merged
merged 3 commits into from
Feb 20, 2020
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
32 changes: 32 additions & 0 deletions src/EFCore.Abstractions/BackingFieldAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// Names the backing field associated with this property.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class BackingFieldAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="BackingFieldAttribute" /> class.
/// </summary>
/// <param name="name"> The name of the backing field. </param>
public BackingFieldAttribute([NotNull] string name)
{
Check.NotEmpty(name, nameof(name));

Name = name;
}

/// <summary>
/// The name of the backing field.
/// </summary>
public string Name { get; }
}
}
42 changes: 42 additions & 0 deletions src/EFCore/Metadata/Conventions/BackingFieldAttributeConvention.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
{
/// <summary>
/// A convention that configures a property as having a backing field
/// based on the <see cref="BackingFieldAttribute"/> attribute.
/// </summary>
public class BackingFieldAttributeConvention : PropertyAttributeConventionBase<BackingFieldAttribute>
{
/// <summary>
/// Creates a new instance of <see cref="BackingFieldAttributeConvention" />.
/// </summary>
/// <param name="dependencies"> Parameter object containing dependencies for this convention. </param>
public BackingFieldAttributeConvention([NotNull] ProviderConventionSetBuilderDependencies dependencies)
: base(dependencies)
{
}

/// <summary>
/// Called after a property is added to the entity type with an attribute on the associated CLR property or field.
/// </summary>
/// <param name="propertyBuilder"> The builder for the property. </param>
/// <param name="attribute"> The attribute. </param>
/// <param name="clrMember"> The member that has the attribute. </param>
/// <param name="context"> Additional information associated with convention execution. </param>
protected override void ProcessPropertyAdded(
IConventionPropertyBuilder propertyBuilder,
BackingFieldAttribute attribute,
MemberInfo clrMember,
IConventionContext context)
{
propertyBuilder.HasField(attribute.Name, fromDataAnnotation: true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public virtual ConventionSet CreateConventionSet()
var maxLengthAttributeConvention = new MaxLengthAttributeConvention(Dependencies);
var stringLengthAttributeConvention = new StringLengthAttributeConvention(Dependencies);
var timestampAttributeConvention = new TimestampAttributeConvention(Dependencies);
var backingFieldAttributeConvention = new BackingFieldAttributeConvention(Dependencies);

conventionSet.PropertyAddedConventions.Add(backingFieldConvention);
conventionSet.PropertyAddedConventions.Add(concurrencyCheckAttributeConvention);
Expand All @@ -118,6 +119,7 @@ public virtual ConventionSet CreateConventionSet()
conventionSet.PropertyAddedConventions.Add(maxLengthAttributeConvention);
conventionSet.PropertyAddedConventions.Add(stringLengthAttributeConvention);
conventionSet.PropertyAddedConventions.Add(timestampAttributeConvention);
conventionSet.PropertyAddedConventions.Add(backingFieldAttributeConvention);
conventionSet.PropertyAddedConventions.Add(keyAttributeConvention);
conventionSet.PropertyAddedConventions.Add(keyDiscoveryConvention);
conventionSet.PropertyAddedConventions.Add(foreignKeyPropertyDiscoveryConvention);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,34 @@ public void TimestampAttribute_on_field_sets_concurrency_token_with_conventional
Assert.True(entityTypeBuilder.Property<byte[]>(nameof(F.Timestamp)).Metadata.IsConcurrencyToken);
}

[ConditionalFact]
public void BackingFieldAttribute_overrides_configuration_from_convention_source()
{
var entityTypeBuilder = CreateInternalEntityTypeBuilder<A>();

var propertyBuilder = entityTypeBuilder.Property(typeof(int?), "BackingFieldProperty", ConfigurationSource.Explicit);

RunConvention(propertyBuilder);

// also asserts that the default backing field, _backingFieldProperty, was _not_ chosen
Assert.Equal("_backingFieldForAttribute", propertyBuilder.Metadata.GetFieldName());
}

[ConditionalFact]
public void BackingFieldAttribute_does_not_override_configuration_from_explicit_source()
{
var entityTypeBuilder = CreateInternalEntityTypeBuilder<A>();

var propertyBuilder = entityTypeBuilder.Property(typeof(int?), "BackingFieldProperty", ConfigurationSource.Explicit);

propertyBuilder.HasField("_backingFieldForFluentApi", ConfigurationSource.Explicit);

RunConvention(propertyBuilder);

// also asserts that the default backing field, _backingFieldProperty, was _not_ chosen
Assert.Equal("_backingFieldForFluentApi", propertyBuilder.Metadata.GetFieldName());
}

#endregion

[ConditionalFact]
Expand Down Expand Up @@ -537,6 +565,9 @@ private static void RunConvention(InternalPropertyBuilder propertyBuilder)
var context = new ConventionContext<IConventionPropertyBuilder>(
propertyBuilder.Metadata.DeclaringEntityType.Model.ConventionDispatcher);

new BackingFieldConvention(dependencies)
.ProcessPropertyAdded(propertyBuilder, context);

new ConcurrencyCheckAttributeConvention(dependencies)
.ProcessPropertyAdded(propertyBuilder, context);

Expand All @@ -557,6 +588,9 @@ private static void RunConvention(InternalPropertyBuilder propertyBuilder)

new TimestampAttributeConvention(dependencies)
.ProcessPropertyAdded(propertyBuilder, context);

new BackingFieldAttributeConvention(dependencies)
.ProcessPropertyAdded(propertyBuilder, context);
}

private void RunConvention(InternalEntityTypeBuilder entityTypeBuilder)
Expand Down Expand Up @@ -607,6 +641,22 @@ private class A

[Required]
private int? PrivateProperty { get; set; }

private int? _backingFieldProperty; // selected by convention
private int? _backingFieldForAttribute;
private int? _backingFieldForFluentApi;

[BackingField("_backingFieldForAttribute")]
private int? BackingFieldProperty
{
get => _backingFieldForAttribute;
set
{
_backingFieldProperty = value;
_backingFieldForAttribute = value;
_backingFieldForFluentApi = value;
}
}
}

private class B
Expand Down