-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Add an IEntityTypeConfiguration equivalent for owned types #15681
Comments
@samueleresca The configuration for an owned type needs to be done in the |
Putting this on the backlog to consider patterns that allow owned type mappings to be split out like this. |
Hi @ajcvickers, thanks for the support. I've tried to convert the code using the public class ItemEntitySchemaDefinition : IEntityTypeConfiguration<Item>
{
public void Configure(EntityTypeBuilder<Item> builder)
{
builder.ToTable("Items", CatalogContext.DEFAULT_SCHEMA);
builder.HasKey(x => x.Id);
builder.Property(x => x.Name)
.IsRequired();
builder.Property(x => x.Description)
.IsRequired()
.HasMaxLength(1000);
builder.OwnsOne(x => x.Artist, navigationBuilder =>
{
navigationBuilder.WithOwner()
.HasForeignKey(e => e.ArtistId)
.HasConstraintName("FK_Artists");
navigationBuilder.ToTable("Artists");
navigationBuilder.HasKey(e => e.ArtistId);
});
... This is the exception:
|
Plus, I'm not sure about the way that builder.OwnsOne(x => x.Artist, navigationBuilder =>
{
...
navigationBuilder.OwnsOne(x => x.Genre, tb =>
{
...
tb.OwnsOne(x => x.Label, lb => {
...
});
});
}); I think the level of nesting causes a little bit of redundancy? Is there any specific reason why you are keeping this syntax? |
@samueleresca If you are still seeing issues, then please post a small, runnable project/solution or complete code listing that demonstrates the behavior you are seeing. With regard to the model configuration syntax, the reason for it is that it forms a complete configuration for the aggregate; that is, the 'normal' entity type as the aggregate root, and all its owned types as the aggregate children. Each aggregate can be configured differently even if it's children have the same CLR types as another aggregate's children. That being said, we recognize that in many cases an owned type is configured the same way for many aggregates, which is why we do plan to support allowing common configuration. It's just not implemented yet. |
I too am getting this after an upgrade to 3.0.0-preview6.19304.10. The configuration was working fine in 2.2 if that counts as I have a few existing migrations that worked fine and produced the structure I was intending. Happy to supply the code if needed. |
I am having the same issue as well. I created a sample app to reproduce the issue: https://github.com/ChristopherHaws/EFCore3MigrationBug If you delete the existing migration, create a new migration with EF Core 3.0, and compare them it looks like it is failing to migrate from: b1.HasOne("EFCore3MigrationBug.Blog")
.WithMany("Posts")
.HasForeignKey("BlogId")
.OnDelete(DeleteBehavior.Cascade); to: b1.WithOwner()
.HasForeignKey("BlogId"); |
Looks like things have changed in EF Core 3.0. Here's my old code: protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Contact>(contact =>
{
var key = "Id";
var fk = $"{nameof(Contact)}{key}";
void setMany<TRelation>(Expression<Func<Contact,
IEnumerable<TRelation>>> navigationExpression)
where TRelation : class =>
contact.OwnsMany(navigationExpression, collection =>
{
collection
.WithOwner()
.HasForeignKey(fk);
collection.Property<int>(key);
collection.HasKey(key, fk);
});
setMany(c => c.Phones);
setMany(c => c.Emails);
setMany(c => c.Addresses);
});
/* Some other stuff */
builder.Entity<Address>()
.Property(a => a.Country)
.HasColumnType(nameof(SqlDbType.Char));
/* Some more stuff */
} I've updated my code to: void setMany<TRelation>(Expression<Func<Contact,
IEnumerable<TRelation>>> navigationExpression)
where TRelation : class =>
contact.OwnsMany(navigationExpression, collection =>
{
collection
.WithOwner()
.HasForeignKey(fk);
collection.Property<int>(key);
collection.HasKey(key, fk);
}); Now I'm getting the following exception on the
|
This worked: builder.Entity<Contact>(contact =>
{
var key = "Id";
var fk = $"{nameof(Contact)}{key}";
void setMany<TRelation>(Expression<Func<Contact,
IEnumerable<TRelation>>> navigationExpression)
where TRelation : class =>
contact.OwnsMany(navigationExpression, navBuilder =>
{
navBuilder
.WithOwner()
.HasForeignKey(fk);
navBuilder.Property<int>(key);
navBuilder.HasKey(key, fk);
if (navBuilder is OwnedNavigationBuilder<Contact, Address> addressNavBuilder)
addressNavBuilder
.Property(a => a.Country)
.HasColumnType(nameof(SqlDbType.Char))
.HasMaxLength(2)
.IsFixedLength();
});
setMany(c => c.Phones);
setMany(c => c.Emails);
setMany(c => c.Addresses);
}); |
Here's a proposal: public interface IOwnedNavigationConfiguration<TEntity, TOwnedEntity>
where TEntity : class
where TOwnedEntity : class
{
void Configure(OwnedNavigationBuilder<TEntity, TOwnedEntity> ownedBuilder);
}
public interface IOwnedNavigationConfiguration
{
void Configure(OwnedNavigationBuilder ownedBuilder);
}
public static class ModelBuilderExtensions
{
public static EntityTypeBuilder<TEntity> ApplyOwnsOneConfiguration<TEntity, TOwnedEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, TOwnedEntity>> navigationExpression,
IOwnedNavigationConfiguration<TEntity, TOwnedEntity> configuration)
where TEntity : class
where TOwnedEntity : class
{
entityTypeBuilder.OwnsOne(navigationExpression, configuration.Configure);
return entityTypeBuilder;
}
public static EntityTypeBuilder<TEntity> ApplyOwnsManyConfiguration<TEntity, TOwnedEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, IEnumerable<TOwnedEntity>>> navigationExpression,
IOwnedNavigationConfiguration<TEntity, TOwnedEntity> configuration)
where TEntity : class
where TOwnedEntity : class
{
entityTypeBuilder.OwnsMany(navigationExpression, configuration.Configure);
return entityTypeBuilder;
}
public static EntityTypeBuilder<TEntity> ApplyOwnsOneConfiguration<TEntity, TOwnedEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, TOwnedEntity>> navigationExpression,
IOwnedNavigationConfiguration configuration)
where TEntity : class
where TOwnedEntity : class
{
entityTypeBuilder.OwnsOne(navigationExpression, configuration.Configure);
return entityTypeBuilder;
}
public static EntityTypeBuilder<TEntity> ApplyOwnsManyConfiguration<TEntity, TOwnedEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, IEnumerable<TOwnedEntity>>> navigationExpression,
IOwnedNavigationConfiguration configuration)
where TEntity : class
where TOwnedEntity : class
{
entityTypeBuilder.OwnsMany(navigationExpression, configuration.Configure);
return entityTypeBuilder;
}
public static OwnedNavigationBuilder<TEntity, TOwnedEntity> ApplyOwnsOneConfiguration<TEntity, TOwnedEntity, TNewOwnedEntity>(
this OwnedNavigationBuilder<TEntity, TOwnedEntity> entityTypeBuilder,
Expression<Func<TOwnedEntity, TNewOwnedEntity>> navigationExpression,
IOwnedNavigationConfiguration<TOwnedEntity, TNewOwnedEntity> configuration)
where TEntity : class
where TOwnedEntity : class
where TNewOwnedEntity : class
{
entityTypeBuilder.OwnsOne(navigationExpression, configuration.Configure);
return entityTypeBuilder;
}
public static OwnedNavigationBuilder<TEntity, TOwnedEntity> ApplyOwnsManyConfiguration<TEntity, TOwnedEntity, TNewOwnedEntity>(
this OwnedNavigationBuilder<TEntity, TOwnedEntity> entityTypeBuilder,
Expression<Func<TOwnedEntity, IEnumerable<TNewOwnedEntity>>> navigationExpression,
IOwnedNavigationConfiguration<TOwnedEntity, TNewOwnedEntity> configuration)
where TEntity : class
where TOwnedEntity : class
where TNewOwnedEntity : class
{
entityTypeBuilder.OwnsMany(navigationExpression, configuration.Configure);
return entityTypeBuilder;
}
public static OwnedNavigationBuilder<TEntity, TOwnedEntity> ApplyOwnsOneConfiguration<TEntity, TOwnedEntity, TNewOwnedEntity>(
this OwnedNavigationBuilder<TEntity, TOwnedEntity> entityTypeBuilder,
Expression<Func<TOwnedEntity, TNewOwnedEntity>> navigationExpression,
IOwnedNavigationConfiguration configuration)
where TEntity : class
where TOwnedEntity : class
where TNewOwnedEntity : class
{
entityTypeBuilder.OwnsOne(navigationExpression, configuration.Configure);
return entityTypeBuilder;
}
public static OwnedNavigationBuilder<TEntity, TOwnedEntity> ApplyOwnsManyConfiguration<TEntity, TOwnedEntity, TNewOwnedEntity>(
this OwnedNavigationBuilder<TEntity, TOwnedEntity> entityTypeBuilder,
Expression<Func<TOwnedEntity, IEnumerable<TNewOwnedEntity>>> navigationExpression,
IOwnedNavigationConfiguration configuration)
where TEntity : class
where TOwnedEntity : class
where TNewOwnedEntity : class
{
entityTypeBuilder.OwnsMany(navigationExpression, configuration.Configure);
return entityTypeBuilder;
}
} It would be used like this: public class BuyerConfiguration : IOwnedNavigationConfiguration<Order, Buyer>
{
public void Configure(OwnedNavigationBuilder<Order, Buyer> buyerConfiguration)
{
buyerConfiguration.Property<int>("OtherId");
}
}
public sealed class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> orderConfiguration)
{
orderConfiguration.ApplyOwnsOneConfiguration(e => e.Buyer, new BuyerConfiguration());
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new OrderConfiguration());
} |
Can't do it for owned types apparently. See dotnet/efcore#15681
I think the goalis to be able to configure common properties of an owned type accross all aggregates that reference it, so this proposal miss the point here. For instance if we have Person, Company and Address classes like this.
The Address class is a owned type of both Person and Company and for both , its Street property should be 10 characters max. Currently, we will have two configurations for both Person and Company and within each one, we must specify that the Street property should be 10 char length max. It will be greate to be able to put those common configuration within one class file like we do in EF6 with ComplexTypeConfiguration. |
@GasyTek - That is being tracked in different issue. See #6787 (comment) |
I updated the proposal with a non-generic interface that can be shared between multiple owned types. |
I implemented the following entities:
I've mapped the entities using the following schemas definitions: SchemaDefinitions.cs
When I run some in-memory tests I get the following exception:
Further technical details
EF Core version:
3.0.0-preview5.19227.1
Database Provider:
Microsoft.EntityFrameworkCore.SqlServer
Operating system: MacOS
IDE: Rider
The text was updated successfully, but these errors were encountered: