0

I am working on a project using .Net 7 and EF Core 7.0.2. I would like to use the new feature json columns but in the database is persisted wrong.

Here is my model:

[Serializable]
    public class Customer
    {
        public long CustomerId { get; }
        public string CustomerName { get; }
        public CustomerDetail CustomerDetails { get; }
        public DateTime CreatedAt { get; }

        private Customer()
        {
            CustomerDetails = CustomerDetail.Empty;
            CustomerName = string.Empty;
        }

        public Customer(long customerId, string customerName, CustomerDetail customerDetails, DateTime createdAt)
        {
            CustomerId = customerId;
            CustomerName = customerName;
            CustomerDetails = customerDetails;
            CreatedAt = createdAt;
        }
    }

    [Serializable]
    public class CustomerDetail
    {
        private readonly List<Order> _orders;
        public int LanguageId { get; }
        public string CurrencySysname { get; }
        public Card? PaymentMethod { get; }
        public Address? Address { get; }
        public IReadOnlyCollection<Order> Orders => _orders.ToArray();

        private CustomerDetail()
        {
            CurrencySysname = string.Empty;
            _orders = new List<Order>();
        }

        public CustomerDetail(IReadOnlyCollection<Order> orders, int languageId, string currencySysname, Card? paymentMethod, Address? address)
        {
            _orders = orders.ToList();
            LanguageId = languageId;
            CurrencySysname = currencySysname;
            PaymentMethod = paymentMethod;
            Address = address;
        }

        public static readonly CustomerDetail Empty = new CustomerDetail();
    }

    [Serializable]
    public class Address
    {
        public int CityId { get; }
        public int PostalCode { get; }

        private Address()
        {

        }

        public Address(int cityId, int postalCode)
        {
            CityId = cityId;
            PostalCode = postalCode;
        }
    }

    [Serializable]
    public class Card
    {
        public int CardType { get; }

        private Card()
        {

        }

        public Card(int cardType)
        {
            CardType = cardType;
        }
    }

    [Serializable]
    public class Order
    {
        public int OrderId { get; }
        public int Amount { get; }

        private Order()
        {

        }

        public Order(int orderId, int amount)
        {
            OrderId = orderId;
            Amount = amount;
        }
    }

about the database configuration is :

class CustomerConfiguration : IEntityTypeConfiguration<Customer>
    {
        public const string Table = "Customers";

        public void Configure(EntityTypeBuilder<Customer> builder)
        {
            builder.ToTable(Table);
            builder.HasKey(x => x.CustomerId);

            builder.Property(x => x.CustomerId).ValueGeneratedNever().IsRequired();
            builder.Property(x => x.CustomerName).IsRequired();
            builder.Property(x => x.CreatedAt).IsRequired();

            builder.OwnsOne(x => x.CustomerDetails, details =>
            {
                details.ToJson();
                details.OwnsOne(x => x.Address);
                details.OwnsOne(x => x.PaymentMethod);
                details.OwnsMany(x => x.Orders);
            });
        }
    }

and when I generate the migration the result is that it semms correct :

public partial class Addcustomer : Migration
    {
        /// <inheritdoc />
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Customers",
                columns: table => new
                {
                    CustomerId = table.Column<long>(type: "bigint", nullable: false),
                    CustomerName = table.Column<string>(type: "nvarchar(max)", nullable: false),
                    CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
                    CustomerDetails = table.Column<string>(type: "nvarchar(max)", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Customers", x => x.CustomerId);
                });
        }

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

Finally when i am going to write in the database

[HttpGet("AddCustomer")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        public async Task<IActionResult> AddCustomerAsync()
        {
            var orders = new List<Order>()
            {
                new Order(orderId: 1, amount: 4),
                new Order(orderId: 2, amount: 5),
            };

            var customer = new Customer(
                customerId: 123,
                customerName: "Jim",
                new CustomerDetail(
                    orders: orders,
                    languageId: 3,
                    currencySysname:"EUR",
                    paymentMethod: new Card(cardType: 1),
                    address: new Address(cityId: 3, postalCode: 123)),
                createdAt: DateTime.Now);

            using var db = new TestDbContext(_options);
            db.Customers.Add(customer);

            await db.SaveChangesAsync();

            return Ok($"Pong - {DateTime.UtcNow:o}");
        }

the result in the database is https://prnt.sc/dyxyBF1oSp52 the objects address, orders payment method are empty.

{"Address":{},"Orders":[{},{}],"PaymentMethod":{}}

I try to understand what is going wrong, thanks in advance!

EF Core version: 7.0.2 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: .NET 7.0 Operating system: Windows 11 IDE: Visual Studio 2022 17.4.3

dimmits
  • 1,999
  • 3
  • 12
  • 30

2 Answers2

0

The problem is with your model, you are using get for all properties, but not set, so the corresponding property values will not be added to your database.

Please add set for each property of the model:

[Serializable]
    public class Customer
    {
        public long CustomerId { get; set; }
        public string CustomerName { get; set; }
        public CustomerDetail CustomerDetails { get; set; }
        public DateTime CreatedAt { get; set; }

        public Customer()
        {
            CustomerDetails = CustomerDetail.Empty;
            CustomerName = string.Empty;
        }

        public Customer(long customerId, string customerName, CustomerDetail customerDetails, DateTime createdAt)
        {
            
            CustomerId = customerId;
            CustomerName = customerName;
            CustomerDetails = customerDetails;
            CreatedAt = createdAt;
        }
    }

    [Serializable]
    public class CustomerDetail
    {
        private readonly List<Order> _orders;
        public int LanguageId { get; set; }
        public string CurrencySysname { get; set; }
        public Card? PaymentMethod { get; set; }
        public Address? Address { get; set; }
        public IReadOnlyCollection<Order> Orders => _orders.ToArray();

        private CustomerDetail()
        {
            CurrencySysname = string.Empty;
            _orders = new List<Order>();
        }
        public CustomerDetail(IReadOnlyCollection<Order> orders, int languageId, string currencySysname, Card? paymentMethod, Address? address)
        {
            _orders = orders.ToList();
            LanguageId = languageId;
            CurrencySysname = currencySysname;
            PaymentMethod = paymentMethod;
            Address = address;
        }
        public static readonly CustomerDetail Empty = new CustomerDetail();
    }

    [Serializable]
    public class Address
    {
        public int CityId { get; set; }
        public int PostalCode { get; set; }

        private Address()
        {

        }

        public Address(int cityId, int postalCode)
        {
            CityId = cityId;
            PostalCode = postalCode;
        }
    }

    [Serializable]
    public class Card
    {
        public int CardType { get; set; }

        private Card()
        {

        }

        public Card(int cardType)
        {
            CardType = cardType;
        }
    }

    [Serializable]
    public class Order
    {
        public int OrderId { get; set; }
        public int Amount { get; set; }

        private Order()
        {

        }

        public Order(int orderId, int amount)
        {
            OrderId = orderId;
            Amount = amount;
        }
    }

Test Result: enter image description here

Edit:

You didn't use set on the property, which means it only has read-only properties, so you can't set its value, so the field you save in database is empty.

You have to keep in mind that a property is just syntactic sugar for a pair of methods. One method (the getter) returns a value of the property type and one method (the setter) accepts a value of the property type.

So, there isn't really any way in general to "set" a property that doesn't have a setter.

Refer to Kyle's answer.

Chen
  • 4,499
  • 1
  • 2
  • 9
  • Thank you very much for your comment, do you know why it happens? because I would like to encapsulate my properties and initialize them only through the constructor. Is there any workaround to use it with { get; } only properties? – dimmits Jan 25 '23 at 07:26
  • Hi @dimmits, I updated my answer, please check it. – Chen Jan 25 '23 at 09:57
0

Also open a ticket in ef core repo and microsoft team answers in this question that :

Only read-write properties are mapped by convention. (But see #

4356.) Any property can be mapped by including it explicitly in the entity type mapping. For example:

details.Property(x => x.CurrencySysname);
dimmits
  • 1,999
  • 3
  • 12
  • 30