EF Core Backing fields - expose property as another type?

c# entity-framework-core

Question

Assume I have an EF entity class Person, with a PhoneNumber on it. PhoneNumber is stored as a string type, but I want all accesses on the Person to go through Phone which has some nice accessor functions, e.g. validation or GetAreaCode(). I want to back it in the db as a string, but when queried for it I want to return it as a PhoneNumber:

public class Person {
    public PhoneNumber Phone { /* Some clever get/set logic here */ }

    private string _phoneNumber; // Backing field
}

Or can I get PhoneNumber to store itself as a string? If I simply include it in the model by removing the backing field above, EF gets confused by the constructors (a protected ctor with some more args than the one string) and also a copy ctor PhoneNumber(PhoneNumber other). Can i make EF ignore those somehow?

I'm open to ideas...

1
6
5/16/2018 3:47:32 PM

Accepted Answer

The only way I've found that works is to create a public property with getters/setters with a name that does not match your backing field and mark it as NotMapped, like so:

    [NotMapped]
    [Display(Name = "Fire Type")]
    public Enums.FireType Type
    {
        get
        {
            Enums.FireType type;
            if (!Enum.TryParse(_fireType, out type))
                type = Enums.FireType.Fire; // default

            return type;
        }
        set
        {
            _fireType = value.ToString();
        }
    }

    private string _fireType;

Then in your DbContext's OnModelCreating method, tell it to create a column on the database table that acts like a backing property:

        // backing properties
        modelBuilder.Entity<Fire>()
            .Property<string>("FireType")
            .HasField("_fireType")
            .UsePropertyAccessMode(PropertyAccessMode.Field);

With that, I was finally able to create a successful migration that allowed me to have a private field on my model, a public transformative property on the model, and a single properly-named column in my database table. The only catch is that the public property and the private field can't share the same name (without sharing the same type), but that wasn't going to be the case for you, anyway.

6
3/29/2018 9:04:55 PM

Popular Answer

You can use @nbrosz's answer to fix your issue but you no longer need to do this kind of workaround if you're using EF Core 2.1. You can rid of the backing field by using EF Core 2.1 (which is in Release Candidate 1 since 7 May 2018) you can use the feature of Value Conversion explained here by Microsoft:

Value converters allow property values to be converted when reading from or writing to the database. This conversion can be from one value to another of the same type (for example, encrypting strings) or from a value of one type to a value of another type (for example, converting enum values to and from strings in the database.)

So for your case, you can just remove the backing field. You no longer need it. Your class should look like this:

public class Person 
{
    public PhoneNumber Phone { /* Some clever get/set logic here */ }
}

And in your OnModelCreating method, you configure the conversion like below:

modelBuilder.Entity<Person>()
    .Property(p => p.Phone)
    .HasConversion(
        phone => { 
            // Here you code the logic of how to get the value to store in DB
            return ...;
        },
        dbValue => { 
            // Here you code the logic of how to construct the PhoneNumber instance from the value to store in DB
        }
    );

That's it. Actually it is in release candidate but Microsoft says:

EF Core 2.1 RC1 is a “go live” release, which means once you test that your application works correctly with RC1, you can use it in production and obtain support from Microsoft, but you should still update to the final stable release once it’s available.

This remainder of my answer is for @nbrosz because you are dealing with enum type. You can remove the backing field and also you can one of the many built-in value converters provided by EF Core 2.1. For enum to string value conversion we have the type EnumToStringConverter. For the logic you're doing in your answer, you can just simplify it to this for entity:

[Display(Name = "Fire Type")]
public Enums.FireType Type { get; set; }

We removed the NotMapped attribute on the property and there is no logic y for conversion.

In your OnModelCreating method you do this:

var converter = new EnumToStringConverter<FireType>();

modelBuilder
    .Entity<Fire>()
    .Property(e => e.FireType)
    .HasConversion(converter);

You can also let EF Core detects the right converter for you by using the generic version of HasConversion<T> like below:

modelBuilder
    .Entity<Fire>()
    .Property(e => e.FireType)
    .HasConversion<string>();

If you don't like to use fluent configuration you can use Column data annotation attibute like below and EF Core will do the conversion for you:

[Column(TypeName = "nvarchar(20)")]
[Display(Name = "Fire Type")]
public Enums.FireType Type { get; set; }


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow