EF Core / DbContext > Map custom type as primary k

2019-05-06 04:36发布

问题:

Using the fluent api, how do I map a custom type as the primary key within my OnModelCreating method of the DbContext class?

Using EF Core I'm trying to build a model for the follow entity.

public class Account
{
    public AccountId AccountId { get; }

    public string Name { get; set; }

    private Account()
    {
    }

    public Account(AccountId accountId, string name)
    {
        AccountId = accountId;
        Name = name;            
    }
}

Where the primary key is the AccountId; the type is a simple value object like this.

public class AccountId
{
    public string Id { get; }

    public AccountId(string accountId)
    {
        Id = accountId;
    }
}

Within OnModelCreating, I found I can't map the AccountId without having a backing field. So I introduced the backing field _accountId. I don't want the AccountId to have a setter.

public class Account
{
    private string _accountId;
    public AccountId AccountId { get { return new AccountId(_accountId); } }

    public string Name { get; set; }

    private Account()
    {
    }

    public Account(AccountId accountId, string name)
    {
        _accountId = accountId.Id;
        Name = name;            
    }
}

But I still can't figure out how you specify a property with a backing field which is also the primary key.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    var account = modelBuilder.Entity<Account>();

    account.ToTable("Accounts");
    account.HasKey(x => x.AccountId);
    account.Property(x => x.AccountId).HasField("_accountId");
}

The OnModelCreating throws an exception on the property map line (account.Property(x => x.AccountId).HasField("_accountId");). Stating that property and field have to be the same type.

回答1:

As pointed out, one can use a custom typed property as entity key by taking advantage of the Value Conversion feature in EF Core 2.1

So in your own example instead of mapping the property to a backing field, you can now define a custom conversion for it like this:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    ...
    account.HasKey(x => x.AccountId);
    account.Property(x => x.AccountId)
        .HasConversion(
            v => v.Id,
            v => new AccountId(v));
}

As described in the documentation, also a ValueConverter class can be implemented to make the conversion reusable and many custom converters are also provided out of the box.

Note: It's a good idea to implement IComparable and IComparable<T> for your custom AccountId class. Because EF Core seems to sort your changed entities based on their Keys internally for batch operations and you would receive an exception if your Key is not comparable!