Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Never insert/update value for Owned type which defined on base for TPH. #896

Open
ShawnChenOfficial opened this issue Jul 21, 2022 · 3 comments
Labels

Comments

@ShawnChenOfficial
Copy link

ShawnChenOfficial commented Jul 21, 2022

I am currently using version 6.5.4, experiencing an issue while implementing a TPH structure.
An Owned type is defined on a base Entity and the value of Owned Type for the extension entity type will never be saved in the database.

// Entities
public class OwnedTypeObject
{
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
}

public class Base
{
    public int Id { get; set; }

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

public class Extension : Base
{
    public string ExtensionPropertyA { get; set; }
    public string ExtensionPropertyB { get; set; }
    public string ExtensionPropertyC { get; set; }
}

// Db Configurations
public class BaseConfiguration : IEntityTypeConfiguration<Base>
{
    public void Configure(EntityTypeBuilder<Base> builder)
    {
        builder.HasKey(h => h.Id);

        builder.OwnsOne(o => o.OwnedTypeObject);
    }
}

public class ExtensionConfiguration : IEntityTypeConfiguration<Extension>
{
    public void Configure(EntityTypeBuilder<Extension> builder)
    {
        // ...configurations for entities
    }
}

then while I was debugging, I found this from SqlServerAdapter.cs

 var ownedEntityType = context.Model.FindEntityType(property.PropertyType);
 if (ownedEntityType == null)
 {
     ownedEntityType = context.Model.GetEntityTypes().SingleOrDefault(x => x.ClrType == property.PropertyType && 
     x.Name.StartsWith(entityType.Name + "." + property.Name + "#"));
 }

and wonder maybe this will never find those Owned Types, from the DB context, and will not add to DataTable to save those values which are the properties of the Owned type.

Or if there are any other suggestions around this issue, will much be appreciated.

@ShawnChenOfficial ShawnChenOfficial closed this as not planned Won't fix, can't repro, duplicate, stale Jul 21, 2022
@borisdj
Copy link
Owner

borisdj commented Jul 22, 2022

Try moving config to attribute,
comment this: builder.OwnsOne
and add [Owned] public class OwnedTypeObject

@jharrison-osv
Copy link

I believe I am running into this issue as well. I have the following configuration, with a table-per-hierarchy structure, where both Person and Business inherit from the base Contact entity. The base Contact entity owns two Address types, the PhysicalAddress and MailingAddress.

public abstract class Contact
{
    public int Id { get; set; }
    public Address PhysicalAddress { get; set; } // owned type
    public Address MailingAddress { get; set; } // owned type

}

public class Person : Contact
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Business : Contact
{
    public string BusinessName { get; set; }
}

public class Address
{
    public string StreetAddress { get; set; }
    public string CityStateZip { get; set; }
}
public class DatabaseContext : DbContext
{
    public DatabaseContext() { }

    public DbSet<Contact> Contacts { get; set; }

    public DbSet<Person> People { get; set; }

    public DbSet<Business> Businesses { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        ConfigureContactEntity(modelBuilder);
        ConfigurePersonEntity(modelBuilder);
        ConfigureBusinessEntity(modelBuilder);

        base.OnModelCreating(modelBuilder);
    }

    private void ConfigureContactEntity(ModelBuilder modelBuilder)
    {
        var builder = modelBuilder.Entity<Contact>();

        builder.HasKey(p => p.Id);

        builder.OwnsOne(a => a.PhysicalAddress, a =>
        {
            a.Property(s => s.StreetAddress).HasMaxLength(50);
            a.Property(s => s.CityStateZip).HasMaxLength(100);
        });

        builder.OwnsOne(a => a.MailingAddress, a =>
        {
            a.Property(s => s.StreetAddress).HasMaxLength(50);
            a.Property(s => s.CityStateZip).HasMaxLength(100);
        });

        builder.HasDiscriminator<string>("Type")
            .HasValue<Business>("Business")
            .HasValue<Person>("Person");
    }

    private void ConfigurePersonEntity(ModelBuilder modelBuilder)
    {
        var builder = modelBuilder.Entity<Person>();
        builder.Property(c => c.FirstName).HasMaxLength(50);
        builder.Property(c => c.LastName).HasMaxLength(50);
    }

    private void ConfigureBusinessEntity(ModelBuilder modelBuilder)
    {
        var builder = modelBuilder.Entity<Business>();
        builder.Property(i => i.BusinessName).HasMaxLength(100);
    }
}

Then run the following code to attempt to do a Bulk Insert.

using var db = new DatabaseContext();

var people = new List<Person>();

for (var i = 1; i <= 100; i++)
{
    var person = new Person
    {
        FirstName = "John",
        LastName =  "Smith",
        PhysicalAddress = new Address
        {
            StreetAddress = "301 S Main St",
            CityStateZip = "Lillington, NE 27546"
        },
        MailingAddress = new Address
        {
            StreetAddress = "1239 9th Ave",
            CityStateZip = "San Francisco, CA 94122"
        },
    };

    people.Add(person);
}

db.BulkInsert(people);

The code that inserts records is providing null values for the owned types (e.g. PhysicalAddress_StreetAddress and PhysicalAddress_CityStateZip are null). In my instance an exception is thrown because those fields are required.

In my experimenting, I noticed that if Contact has just one instance of the owned type (that is, if it just has the PhyiscalAddress property, and not the additional MailingAddress property), then it will populate the fields and the inserts will be successful.

I tried to change the configuration to remove the OwnsOne methods from the model builder configuration, and add the [Owned] attribute to the Address class, but that did not seem to have any effect.

I have tried this with the current latest version, 7.0.1. I have created a small repository which should demo this scenario to reproduce the issue here: EFCore.BulkExtensions.OwnedTypeTest

@borisdj
Copy link
Owner

borisdj commented Jul 26, 2023

The issue is caused because of Owned being in Base class in combination with inheritance and discriminator.
In code in SqlServerAdapter ownedEntityType should also search for entityType.BaseType?.Name but objectIdentifier still returns null for columnName.
For now you try to move Owned props directly in child classes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants