I have my entity and owned entity configured with complex primary key using DDD
approach as follows,
public class Key : ValueObject
{
public string Value { get; }
protected Key()
{
}
private Key(string id) : this()
{
Value = id;
}
public static Result<Key> Create(string id)
{
return Result.Success(new Key(id));
}
protected override IEnumerable<object> 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<FolderId> Create(int id)
{
return Result.Success(new FolderId(id));
}
protected override IEnumerable<object> 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<TextPositionY> Create(int position)
{
return Result.Success(new TextPositionY(position));
}
public static Result<TextPositionY> CreateConverted(int position)
{
return Result.Success(new TextPositionY((double)position));
}
protected override IEnumerable<object> 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<PicturePositionX> Create(int position)
{
return Result.Success(new PicturePositionX(position));
}
protected override IEnumerable<object> 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;
}
}
Here is my DBContext,
public class BlogContext : DbContext
{
public DbSet<BaseEntity> 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<BaseEntity>(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);
});
});
}
}
when I try to update the entity as shown below, update happens only on entity column but not on owned entity column.
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<BaseEntity>().Update(ownedEntity);
await ctx.SaveChangesAsync();
}
}
}
Here is the query generated by 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 ignores the column in update. Please assist on what I'm missing.
EF Core version: 3.1.9
Database provider: Microsoft.EntityFrameworkCore.SqlServer 3.1.9
Target framework: .NET CORE 3.1
Operating system: Windows 10
IDE: Visual Studio 2019 16.8
This no longer happens from EF Core 6
. Here is the GitHub issue link for the same.