dotnet / dotnet/efcore

Use the existing complex type instance, if present, when materializing

Open
#32,989 2 comments 0 reactions 0 assignees View on GitHub
area-query customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

I have the usecase of forwarding and reading a value to a complex type by properties (that are also mapped as column). This works great until the entity is loaded from the database. (Persiting the entitiy works as intended!)

The problem boils down to following section in an entity:
```c#
[Required]
public int QuantityType // Redirect value from the complex type.
{
get => Quantity.Type;
set => Quantity.Type = value;
}

public Quantity Quantity { get; set; } = new();
```
The odd behaviour is:
- The setters `QuantityType` and `Quantity.Type` do recive the db value!
- After that, it gets magically reset to '0'! No setter triggered!

Why does the value suddently get reset? Am I absusing this to hard?

![image](https://github.com/dotnet/efcore/assets/15609168/8fbf5ba9-c0d5-4526-aea7-65ffa745ba19)
I renamed the entities, sorry for the outdated screen shot.

Why am i doing this / need this? (My specific usecase...)

If you have a better aproched to this.. im open for any idears!

I abstract my unit value scale variable. And I can provide scaled values based on the unit type and scale provided.
Otherwise I have to declare always the same variable for holding the scale and converting that according to the type.

```C#
///
/// Reqpresents an unit amount of a unit. Holds the currently used scale and raw value of the quantity.
///
[ComplexType]
public class QuantityValue
{
///
/// Amount of the quantity with standard scaling for any unit.
/// This is stored in the database to enable comparisions.
///
[Required]
[Range(0, double.MaxValue)]
public double Value { get; private set; }

///
/// Unit scale of the value.
/// Stored as integer because the enum is changed based on .
///
[Required]
public int Scale { get; private set; }

///
/// Quantity type that is used for the value
///
[NotMapped]
public Quantity Type { get; set; }

///
/// Unit scale of the value.
/// Current enum entry for the quantity type, based on and
///
[NotMapped]
public Enum? ScaleEnum
{
get => Units.Deserialize(Scale, Type);
set => Scale = Units.Serialize(value);
}

///
/// Scaled value of the quantity based on .
///
[NotMapped]
public double ValueScaled
{
get => Units.ScaleFromStandard(Value, Type, ScaleEnum);
set => Value = Units.ScaleToStandard(value, Type, ScaleEnum);
}
}
```

## Code for reproduction
Migrations are required
```C#
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ConsoleApp4
{
internal static class Program4
{
public static void Test(string[] args)
{
var original = new Foo() { QuantityType = 128 };

using (var ctx = new CustomContext4())
{
ctx.Database.Migrate();
ctx.Add(original);
ctx.SaveChanges();
}

using (var ctx = new CustomContext4())
{
var item = ctx.Foos.FirstOrDefault();
// Items got loaded. The setter recieved 128,
// But it magically got reset to default!?
if (item?.QuantityType != original.QuantityType)
throw new Exception();
}

Console.WriteLine("Done!");
Console.ReadLine();
}

public class CustomContext4 : DbContext
{
public DbSet Foos { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
builder.LogTo(Console.WriteLine, new[] { RelationalEventId.CommandExecuting }).EnableSensitiveDataLogging();
builder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=TestDb;Trusted_Connection=True");
}
}

public class Foo
{
[Key]
public int Id { get; set; }

[Required]
public int QuantityType // Redirect value from the complex type.
{
get => Quantity.Type;
set => Quantity.Type = value; // Recives here actually 128 on FirstOrDefault()!
}

public Quantity Quantity { get; set; } = new();
}

[ComplexType]
public class Quantity
{
[NotMapped]
public int Type { get; set; } // This is set from outside. Recieve 128 in setter!

[Required]
public int Scale { get; set; }

[Required]
public int Value { get; set; }
}
}
}
```

### Include provider and version information
EF Core version: 8.01
Database provider: Microsoft.EntityFrameworkCore.SqlServer

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.