-
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
Allow mapping a key or index over more than one column across owned entity types. #11336
Comments
Notes from triage: putting this on the backlog to consider supporting indexes and table splitting together. The implementation will likely require an internal store-side model. @lincolnzocateli The workaround is to create the index in the migration, rather than in the model. |
Ok I'm following the backlog |
@lincolnzocateli Another workaround is to create a shadow property on the owned type and map it to the same column: builder.OwnsOne(x => x.Address, ab =>
{
ab.Property<string>("Name")
.HasColumnName("Name")
.IsRequired()
.HasColumnType("varchar(40)");
ab.HasIndex(
"Name",
"AddressType",
"Zip",
).HasName("IX_MyIndex")
.IsUnique();
} Though since context.Entry(contact).Reference(e => e.Address).TargetEntry.Property<string>("Name")
.CurrentValue = contact.Name; |
@AndriySvyryd Thanks for the tip, I tried to do it, but I did not find a viable solution for having to populate it manually when adding a new Address. Many people in the dotnet community in Brazil have asked me about a solution to this, for now we are waiting for the backlog. |
@lincolnzocateli Why didn't my workaround for populating the shadow property work for you? Is that you don't have the context available when a new address is added? If so you could go through all entries and find which ones were added: foreach (var entityEntry in context.ChangeTracker.Entries())
{
if (entityEntry.Entity.GetType() != typeof(Contact))
{
continue;
}
var addressEntry = entityEntry.Reference("Address").TargetEntry;
if (addressEntry.State == EntityState.Added
|| addressEntry.State == EntityState.Detached)
{
addressEntry.Property("Name").CurrentValue = entityEntry.Property("Name").CurrentValue;
}
} |
@AndriySvyryd, I'm doing well with your solution, thanks for the help. |
We wanted to keep this open in the backlog. |
@AndriySvyryd does this workaround require an additional column in the table? I've adapted your above code to my own solution by adding a shadow property on "Name", but it's creating two columns, one prefixed with "MyTableName_". |
@dylinmaust - It does not require additional column. The point is you create a shadow property in your complex type and configure it using |
@smitpatel Thanks for the clarification. Here's my configuration:
which is generating something like this:
|
Can you make following change and see if it works for you? modelBuilder
.Property(p => p.Name)
.HasColumnName("Name") // <<-- this line is added by me
.IsRequired()
.HasMaxLength(100); |
That was an embarrassingly simple solution. Thank you! And thanks for the Markdown help 👍 |
Looking forward to see this feature added. |
Lack of this discourages me from using owned types. Has this been considered and rejected from 3.0 scope? Is "backlog" equivalent to "maybe after 3.0" in this sense? I am also interested in owned property referencing support in |
+1 need this |
Any news? |
There are many other improvements that have bigger impact than this, so we won't get to this one any time soon. Use the 👍 reaction on the first post to indicate your support, this is one of the ways we measure impact. |
Any update on when this is going to be resolved? |
Any news? |
This issue is in the backlog milestone, which means we don't plan to work on it for 5.0; once that's released, we'll reexamine which issues can make it into 6.0. Note that this issue has an easy workaround - simply create the index in your migration - which means we generally don't consider this to be extremely high priority. |
I wish we could at least create property accessors over the shadow properties of the the owned entity which should then allow the creation of an index using the index attribute. |
I have another workaround that may be useful for somebody if OwnedEntity can be treated as a ValueObject and it is acceptable to configure owned entity by using setters and getters instead of using OwnsOne api.
then in the Entity configuration:
|
Here is my findings. It is possible to tamper the metadata using internal APIs to force it generate correct migrations code but there are two drawbacks:
To do this you need to get the references to corresponding instances of The example: // ...
using Index = Microsoft.EntityFrameworkCore.Metadata.Internal.Index;
using Property = Microsoft.EntityFrameworkCore.Metadata.Internal.Property;
// ...
public class ProdAttr
{
public Guid Id { get; set; }
public Guid ProdRefId { get; set; }
public string Name { get; set; } = null!;
public bool IsMany { get; set; }
public Val Value { get; set; } = null!;
public record Val(Guid? File)
{
public FileAsset? FileRef { get; init; }
}
}
partial class EntityTypeConfiguration : IEntityTypeConfiguration<ProdAttr>
{
// Suppress warning on usages of Index and Property types.
[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")]
public void Configure(EntityTypeBuilder<ProdAttr> builder)
{
builder.ToTable("prod_attr", "catalog");
builder.Property(t => t.Id).HasColumnName("prod_attr_id");
// Store the Property references. The Metadata property is of type
// Microsoft.EntityFrameworkCore.Metadata.Internal.Property which implements IMutableProperty.
Property prodRefIdProperty = (Property)builder.Property(t => t.ProdRefId).HasColumnName("prod_refid").Metadata;
Property nameProperty = (Property)builder.Property(t => t.Name).HasColumnName("name").Metadata;
builder.Property(t => t.IsMany).HasColumnName("is_many").HasDefaultValue(false).ValueGeneratedNever();
builder.HasKey(t => t.Id);
builder.HasIndex(t => new { t.ProdRefId, t.Name });
builder.HasIndex(t => t.Name);
builder.OwnsOne(t => t.Value, o =>
{
// Store the Property reference.
Property fileProperty = (Property)o.Property(t => t.File).HasColumnName("file_val").Metadata;
o.HasIndex(t => t.File);
o.HasOne(t => t.FileRef).WithMany().HasForeignKey(t => t.File);
// Create the index with bogus property list. Make sure the list is unique or specify the unique name.
// Otherwise you will alter the existing index definition.
// Stote the Index reference. The Metadata property is of type
// Microsoft.EntityFrameworkCore.Metadata.Internal.Index which implements IMutableIndex.
Index index = (Index)builder.HasIndex(t => t.Id, "1120efc7-3d77-4ee6-be7d-a2955c6640ce").IsUnique().HasFilter("file_val IS NOT NULL").Metadata;
// Cast list of properties to concrete run-time type.
List<Property> properties = (List<Property>)index.Properties;
// Clear the list.
properties.Clear();
// Populate the list with desired properties.
properties.AddRange(new[] { prodRefIdProperty, nameProperty, fileProperty });
});
// ...
}
} This will add the index migration code: migrationBuilder.CreateIndex(
name: "1120efc7-3d77-4ee6-be7d-a2955c6640ce",
schema: "catalog",
table: "prod_attr",
columns: new[] { "prod_refid", "name", "file_val" },
unique: true,
filter: "file_val IS NOT NULL"); But also will populate the model snapshot with: b.HasIndex(new[] { "ProdRefId", "Name", "File" }, "1120efc7-3d77-4ee6-be7d-a2955c6640ce")
.IsUnique()
.HasFilter("file_val IS NOT NULL"); making it invalid, because entity This is not a practical solution but could bring some insights. |
Any news to this issue? Any considerations to implement this? |
@kl1mm This issue is in the Backlog milestone. This means that it is not planned for the next release (EF Core 6.0). We will re-assess the backlog following the this release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources. Make sure to vote (👍) for this issue if it is important to you. |
In the case where there is a need to create a composite unique (clustered) index, I've decided to simply not use value objects in those persisted entities to keep things simple and avoid all these complex workarounds, at least until it is correctly supported by EF Core. I prefer to use value objects but not at the cost of this added complexity. So, just put all those properties in your entity/aggregate and save yourself from all this trouble. Had: class MyEntity { Wanted: class MyEntity { class IntId { // immutable logic with validations Ran into limitations from EF Core [this issue] Changed to: still using value object but indirectly class MyEntity { public MyEntity(int incomingId) } class IntId { } |
I need to generate migration for my entity "Contact" has the "Complex Type -> Address".
So far so good!
However, in the "Contact" entity I need to create a unique index containing "Complex Type -> Address" properties, which is causing migration error.
How can I create this unique index?
The code with example is in:
https://github.com/lincolnzocateli/EFCoreExample/blob/master/Map/ContactMap.cs
Exception message:
Steps to reproduce
Generate migration for the first time:
/> dotnet ef migrations add v1.0.0
Further technical details
EF Core version:
Microsoft.EntityFrameworkCore.Design version 2.0.1
Microsoft.EntityFrameworkCore.Tools.DotNet version 2.0.1
Database Provider: Microsoft.EntityFrameworkCore.SqlServer version 2.0.1
Operating system: Linux Debian 9
IDE: VSCODE
The text was updated successfully, but these errors were encountered: