EF Core ignores owned entity column in entity update when the entity primary key has value conversion

Tengo mi entidad y entidad de propiedad configurada con llave primaria compleja utilizando DDD de la manera siguiente:

public class Key : ValueObject
{
    public string Value { get; }

    protected Key()
    {
    }

    private Key(string id) : this()
    {
        Value = id;
    }

    public static Result Create(string id)
    {
        return Result.Success(new Key(id));
    }

    protected override IEnumerable GetEqualityComponents()
    {
        yield return Value;
    }

    public static implicit operator string(Key id)
    {
        return id.Value;
    }
}

public class FolderId : ValueObject
{
    public int Value { get; }

    protected FolderId()
    {
    }

    private FolderId(int value) : this()
    {
        Value = value;
    }

    public static Result Create(int id)
    {
        return Result.Success(new FolderId(id));
    }

    protected override IEnumerable GetEqualityComponents()
    {
        yield return Value;
    }

    public static implicit operator int(FolderId id)
    {
        return id.Value;
    }
}

public class TextPositionY : ValueObject
{
    public int Value { get; }
    public double ConvertedValue { get; }

    protected TextPositionY()
    {
    }

    private TextPositionY(int value) : this()
    {
        Value = value;
    }

    private TextPositionY(double value) : this()
    {
        ConvertedValue = value;
    }

    public static Result Create(int position)
    {
        return Result.Success(new TextPositionY(position));
    }

    public static Result CreateConverted(int position)
    {
        return Result.Success(new TextPositionY((double)position));
    }

    protected override IEnumerable GetEqualityComponents()
    {
        yield return Value;
    }

    public static implicit operator int(TextPositionY id)
    {
        return id.Value;
    }
}

public class PicturePositionX : ValueObject
{
    public int Value { get; }

    protected PicturePositionX()
    {
    }

    private PicturePositionX(int value) : this()
    {
        Value = value;
    }

    public static Result Create(int position)
    {
        return Result.Success(new PicturePositionX(position));
    }

    protected override IEnumerable GetEqualityComponents()
    {
        yield return Value;
    }

    public static implicit operator int(PicturePositionX id)
    {
        return id.Value;
    }
}

public class BaseEntity
{
    protected BaseEntity()
    {
    }

    public BaseEntity(Key key, FolderId folderId, TextEntity text, PictureEntity picture) : this()
    {
        Name = key;
        FolderId = folderId;
        Text = text;
        Picture = picture;
    }

    public Key Name { get; set; }
    public FolderId FolderId { get; private set; }
    public TextEntity Text { get; set; }
    public PictureEntity Picture { get; set; }

    public void Update(FolderId folderId, TextEntity text, PictureEntity picture)
    {
        FolderId = folderId;
        Text = text;
        Picture = picture;
        //Text.Update(text.PositionY);
        //Picture.Update(picture.PositionX);
    }
}

public class TextEntity
{
    protected TextEntity()
    {
    }

    public TextEntity(TextPositionY positionY) : this()
    {
        PositionY = positionY;
    }

    public TextPositionY PositionY { get; private set; }

    public void Update(TextPositionY positionY)
    {
        PositionY = positionY;
    }

}

public class PictureEntity
{
    protected PictureEntity()
    {
    }

    public PictureEntity(PicturePositionX positionX) : this()
    {
        PositionX = positionX;
    }

    public PicturePositionX PositionX { get; private set; }

    public void Update(PicturePositionX positionX)
    {
        PositionX = positionX;
    }

}

Aquí está mi DBContext,

public class BlogContext : DbContext
{
    public DbSet OwnedEntityTest { get; set; }

    static ILoggerFactory ContextLoggerFactory
    => LoggerFactory.Create(b => b.AddConsole().AddFilter("", LogLevel.Information));

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer("Server=.\\;Database=PracticeDB;Trusted_Connection=true;")
            .EnableSensitiveDataLogging()
            .UseLoggerFactory(ContextLoggerFactory);

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity(entity =>
        {
            entity.ToTable("OwnedEntityTest");

            entity.HasKey(e => e.Name);

            entity.Property(p => p.Name)
                .HasColumnName("Id")
                .HasConversion(p => p.Value, p => Key.Create(p).Value);

            entity.Property(p => p.FolderId)
                .HasConversion(p => p.Value, p => FolderId.Create(p).Value);

            entity.OwnsOne(p => p.Picture, p =>
            {
                p.WithOwner();

                p.Property(pp => pp.PositionX)
                    .HasColumnName("PicturePositionX")
                    .HasConversion(p => p.Value, p => PicturePositionX.Create(p).Value);
            });

            entity.OwnsOne(p => p.Text, p =>
            {
                p.WithOwner();

                p.Property(pp => pp.PositionY)
                    .HasColumnName("TextPositionY")
                    .HasConversion(p => p.Value, p => TextPositionY.CreateConverted(p).Value);
            });
        });
    }
}

cuando intento actualizar la entidad como se muestra a continuación, la actualización sólo sucede en la columna de entidad pero no en la columna de entidad propiedad.

class Program
{
    static async Task Main(string[] args)
    {
        using var ctx = new BlogContext();
        //ctx.Database.EnsureDeleted();
        //ctx.Database.EnsureCreated();

        if (!ctx.OwnedEntityTest.Any())
        {
            var folderId = FolderId.Create(1);
            var picturePositionX = PicturePositionX.Create(1);
            var textPositionY = TextPositionY.Create(1);
            var key = Key.Create("1-1-1");

            var text = new TextEntity(textPositionY.Value);
            var picture = new PictureEntity(picturePositionX.Value);

            var ownedEntity = new BaseEntity(key.Value, folderId.Value, text, picture);

            ctx.OwnedEntityTest.Add(ownedEntity);
            await ctx.SaveChangesAsync();
        }
        else
        {
            var key = Key.Create("1-1-1");
            var ownedEntity = await ctx.OwnedEntityTest.FindAsync(key.Value);

            var updatedFolderId = FolderId.Create(1);
            var updatedPicturePositionX = PicturePositionX.Create(1);
            var updatedTextPositionY = TextPositionY.Create(0);

            var updatedText = new TextEntity(updatedTextPositionY.Value);
            var updatedPicture = new PictureEntity(updatedPicturePositionX.Value);

            ownedEntity.Update(updatedFolderId.Value, updatedText, updatedPicture);

            ctx.Set().Update(ownedEntity);
            await ctx.SaveChangesAsync();
        }
    }
}

Aquí está la consulta generada por EF Core:

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (61ms) [Parameters=[@__p_0='1-1-1' (Size = 450)], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [o].[Id], [o].[FolderId], [t].[Id], [t].[PicturePositionX], [t0].[Id], [t0].[TextPositionY]
      FROM [OwnedEntityTest] AS [o]
      LEFT JOIN (
          SELECT [o0].[Id], [o0].[PicturePositionX]
          FROM [OwnedEntityTest] AS [o0]
          WHERE [o0].[PicturePositionX] IS NOT NULL
      ) AS [t] ON [o].[Id] = [t].[Id]
      LEFT JOIN (
          SELECT [o1].[Id], [o1].[TextPositionY]
          FROM [OwnedEntityTest] AS [o1]
          WHERE [o1].[TextPositionY] IS NOT NULL
      ) AS [t0] ON [o].[Id] = [t0].[Id]
      WHERE [o].[Id] = @__p_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (2ms) [Parameters=[@p1='1-1-1' (Nullable = false) (Size = 450), @p0='1' (Nullable = true)], CommandType='Text', CommandTimeout='30']
      SET NOCOUNT ON;
      UPDATE [OwnedEntityTest] SET [FolderId] = @p0
      WHERE [Id] = @p1;
      SELECT @@ROWCOUNT;

Table Schema:

CREATE TABLE [dbo].[OwnedEntityTest](
    [Id] [nvarchar](50) NOT NULL,
    [FolderId] [int] NOT NULL,
    [PicturePositionX] [int] NULL,
    [TextPositionY] [int] NULL,
 CONSTRAINT [PK_OwnedEntityTest] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

EF Core ignora la columna en actualización. Por favor, asista en lo que me estoy perdiendo.

EF Core Details

EF Versión principal: 3.1.9

Proveedor de base de datos: Microsoft.EntityFrameworkCore.SqlServer 3.1.9

Marco de destino: .NET CORE 3.1

Sistema operativo: Windows 10

IDE: Visual Studio 2019 16.8

Pregunta hecha hace 3 años, 5 meses, 0 días - Por scriptsorcerer4f7e


3 Respuestas:

  • Esto ya no sucede de EF Core 6. Aquí está el Enlace de emisión GitHub por lo mismo.

    Respondida el Dec 16, 2020 a las 11:39 - por bugbusterx

    Votos positivos: 0 | Votos negativos: 0

  • El problema que estás experimentando es que al intentar actualizar la entidad propiedad (TextEntity y PictureEntity) dentro de la entidad principal (BaseEntity), EF Core no está detectando correctamente los cambios en esas propiedades para persistirlos en la base de datos.

    Para solucionar este problema, puedes seguir los siguientes pasos:

    • Asegúrate de que las propiedades de las entidades propiedad estén marcadas como públicas en lugar de privadas para que EF Core pueda acceder y rastrear los cambios en esas propiedades.
    • Dentro del método `Update` de las entidades `TextEntity` y `PictureEntity`, asegúrate de llamar al método `Update` de las propiedades correspondientes para indicar a EF Core que se han realizado cambios en esas propiedades.

    Aquí te dejo un ejemplo de cómo podrías ajustar tus entidades para reflejar estos cambios:

    En la clase TextEntity:

    c

    public TextPositionY PositionY { get; set; }

    public void Update(TextPositionY positionY) { PositionY = positionY; base.Update(); // Llama al método Update de la entidad propiedad }

    En la clase PictureEntity:

    c

    public PicturePositionX PositionX { get; set; }

    public void Update(PicturePositionX positionX) { PositionX = positionX; base.Update(); // Llama al método Update de la entidad propiedad }

    Al llamar al método Update de las entidades propiedad después de realizar cambios en sus propiedades, EF Core debería detectar los cambios y persistirlos correctamente en la base de datos.

    Espero que esta solución te ayude a resolver el problema que estás experimentando. ¡Déjame saber si tienes alguna otra pregunta o necesitas más ayuda!

    Respondida el Dec 17, 2020 a las 11:37 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • El problema es que al utilizar Update en DbContext solo actualiza las propiedades de navegación de la entidad principal y no las propiedades de navegación de las entidades propiedad.

    Para actualizar las propiedades de navegación de las entidades propiedad, debes utilizar el método Entry de DbContext y luego llamar a SetProperty en las propiedades de navegación de las entidades propiedad.

    Aquí tienes el código corregido:

    csharp
    using System.Linq;
    using Microsoft.EntityFrameworkCore;
    
    class Program
    {
        static async Task Main(string[] args)
        {
            using var ctx = new BlogContext();
            //ctx.Database.EnsureDeleted();
            //ctx.Database.EnsureCreated();
    
            if (!ctx.OwnedEntityTest.Any())
            {
                var folderId = FolderId.Create(1);
                var picturePositionX = PicturePositionX.Create(1);
                var textPositionY = TextPositionY.Create(1);
                var key = Key.Create("1-1-1");
    
                var text = new TextEntity(textPositionY.Value);
                var picture = new PictureEntity(picturePositionX.Value);
    
                var ownedEntity = new BaseEntity(key.Value, folderId.Value, text, picture);
    
                ctx.OwnedEntityTest.Add(ownedEntity);
                await ctx.SaveChangesAsync();
            }
            else
            {
                var key = Key.Create("1-1-1");
                var ownedEntity = await ctx.OwnedEntityTest.FindAsync(key.Value);
    
                var updatedFolderId = FolderId.Create(1);
                var updatedPicturePositionX = PicturePositionX.Create(1);
                var updatedTextPositionY = TextPositionY.Create(0);
    
                var updatedText = new TextEntity(updatedTextPositionY.Value);
                var updatedPicture = new PictureEntity(updatedPicturePositionX.Value);
    
                var entry = ctx.Entry(ownedEntity);
                entry.SetProperty(e => e.FolderId, updatedFolderId.Value);
                entry.Reference(e => e.Text).CurrentValue = updatedText;
                entry.Reference(e => e.Picture).CurrentValue = updatedPicture;
    
                await ctx.SaveChangesAsync();
            }
        }
    }
    

    Espero que esto ayude. Avísame si tienes alguna otra pregunta.

    Respondida el Dec 17, 2020 a las 11:44 - por Gemini

    Votos positivos: 0 | Votos negativos: 0