.NET 8 owned type w jednej tabeli zamiast oddzielnych

0

Witam

Mam sobie klasę User, która zawiera właściwość PhoneNumber w postaci rekordu, który jest obiektem wartościowym.
PhoneNumber konfiguruję jako "Owned Type". Niestety, ale PhoneNumber podczas generowania migracji jest tworzony jako oddzielna tabela, a nie jako dodatkowa kolumna w tabeli User.

Przejrzałem wszelkie materiały w internecie i wszędzie jest to tworzone jako dodatkowa kolumna zamiast oddzielna tabela.
Np tutaj https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities

Niestety nie mogę użyć "Complex Type" gdyż nie mogę być one "nullowalne".

Jest tu jakiś mądry co poradzi jak to zrobić aby migracja generowała PhoneNumber jako kolumnę w tabeli?

public class User 
{
   public Guid Id { get; private set; }
 
   ...

   public PhoneNumber PhoneNumber { get; private set; }
}

public sealed record PhoneNumber
{
    private PhoneNumber() { }

    public string Value { get; }

    public PhoneNumber(string phoneNumber)
    {
        if (!string.IsNullOrWhiteSpace(phoneNumber) && phoneNumber.Length is < 3 or > 60)
            throw new InvalidPhoneNumberException();

        Value = phoneNumber;
    }

    public static implicit operator PhoneNumber(string phoneNumber) => new(phoneNumber);

    public static implicit operator string(PhoneNumber phoneNumber) => phoneNumber.Value;

    public override string ToString() => Value;

    public bool Contains(string value) => Value.Contains(value);
}


builder.OwnsOne(x => x.PhoneNumber, phoneNumber =>
{
    phoneNumber.Property(p => p.Value)
               .HasColumnName(nameof(User.PhoneNumber))
               .HasMaxLength(300)
               .IsRequired(false);
});

migrationBuilder.CreateTable(
    name: "PhoneNumber",
    schema: "Identity",
    columns: table => new
    {
        UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
        PhoneNumber = table.Column<string>(type: "nvarchar(300)", maxLength: 300, nullable: true)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_PhoneNumber", x => x.UserId);
    });

migrationBuilder.CreateTable(
    name: "User",
    schema: "Identity",
    columns: table => new
    {
        Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
        ...
    },
0

Odpowiedzi nie znam, ale dla mnie to jest mięszanie warstw PhoneNumber z encji powinien być np. stringiem a nie klasą, klasa PhoneNumber niech będzie jakimś DTO i niech ma tam swoją logikę a pomiędzy nimi jakiś mapper czy cokolwiek chcesz.

1

Zastanów się, czy na pewno chcesz tworzyć ValueObject na podstawie typu podłączonego do Entity Frameworka. To się zwykle nie kończy dobrze. Idealnie byłoby, gdyby modele bazodanowe były oddzielnymi klasami, a domenowe oddzielnymi, więc klasa PhoneNumber nie byłaby zależna od żadnej bazy, a DB model mógłby być typu string i wszystko by grało.

Odpowiadając na pytanie, EF nie pozwali wprost migrować typu PhoneNumber do kolumny nvarchar.

0
public class ExampleContext : DbContext
{       

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ExampleDB;Trusted_Connection=True;");           
    }

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

        modelBuilder.Entity<User>().OwnsOne(x => x.PhoneNumber, phoneNumber =>
        {
            phoneNumber.Property(p => p.Value)
                       .HasColumnName(nameof(User.PhoneNumber))
                       .HasMaxLength(300)
                       .IsRequired(false);
        });
    }
}


public sealed record PhoneNumber
{
    private PhoneNumber() { }

    public string Value { get; set; }

    public PhoneNumber(string phoneNumber)
    {
        if (!string.IsNullOrWhiteSpace(phoneNumber) && phoneNumber.Length is < 3 or > 60)
            throw new Exception();

        Value = phoneNumber;
    }

    public static implicit operator PhoneNumber(string phoneNumber) => new(phoneNumber);

    public static implicit operator string(PhoneNumber phoneNumber) => phoneNumber.Value;

    public override string ToString() => Value;

    public bool Contains(string value) => Value.Contains(value);
}

public class User
{
    public int Id { get; set; }
    public PhoneNumber PhoneNumber { get; set; }
}

Taka konfiguracja generuje:

public partial class Migration_1 : Migration
{
    /// <inheritdoc />
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "User",
            columns: table => new
            {
                Id = table.Column<int>(type: "int", nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                PhoneNumber = table.Column<string>(type: "nvarchar(300)", maxLength: 300, nullable: true)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_User", x => x.Id);
            });
    }

    /// <inheritdoc />
    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "User");
    }
}

Różnica jest w postaci publicznego Set-a na polu Value w PhoneNumber i PhoneNumber w User

0
AbcDefGhi napisał(a):
Różnica jest w postaci publicznego Set-a na polu Value w PhoneNumber i PhoneNumber w User

Z definicji obiekt wartościowy jest niezmienny/immutable. Także dorzucenie seta do właściwości Value jest niepożądane.

0
altek napisał(a):
AbcDefGhi napisał(a):
Różnica jest w postaci publicznego Set-a na polu Value w PhoneNumber i PhoneNumber w User

Z definicji obiekt wartościowy jest niezmienny/immutable. Także dorzucenie seta do właściwości Value jest niepożądane.

A możesz zamienić set na init?

1 użytkowników online, w tym zalogowanych: 0, gości: 1