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

Support value generation with converters #11597

Closed
Tracked by #240
ajcvickers opened this issue Apr 9, 2018 · 42 comments · Fixed by #27759
Closed
Tracked by #240

Support value generation with converters #11597

ajcvickers opened this issue Apr 9, 2018 · 42 comments · Fixed by #27759
Labels
area-o/c-mapping closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. providers-beware punted-for-3.0 type-enhancement
Milestone

Comments

@ajcvickers
Copy link
Member

Current behavior is to throw by default if a converter is associated with a property that will use value generation
However, a value generator to use can be specified:

  • On the property
  • On the type mapping
  • On a converter

A client-side generator is added to the Guid converters so that Guid key scenarios still work. We should find similar "safe" cases and make those work as well.

Original PR: #11479

@ajcvickers
Copy link
Member Author

Consider the Oracle case in #11559 when looking into this.

@ajcvickers
Copy link
Member Author

Note: when working on this, also consider #11970

@twsl
Copy link

twsl commented Jan 2, 2019

Is anyone already working on this and is there an ETA? If not, would you accept PR's for this?

@ajcvickers
Copy link
Member Author

@twsI It's in the 3.0 milestone, which means it is tentatively planned for 3.0. I don't believe anyone is working on it right now. We would consider PRs, but it would be helpful if you could provide some details of what you plan to do before submitting a PR, since it's not clear what the behavior here should be or how it should be implemented.

@ajcvickers ajcvickers added this to the Backlog milestone Oct 8, 2021
@ajcvickers ajcvickers modified the milestones: Backlog, 7.0.0 Oct 20, 2021
ajcvickers added a commit that referenced this issue Mar 16, 2022
Part of #11597

This change takes the ValueComparer defined for the principal key and uses it for the foreign key, but also accommodating for nulls appropriately. As part of this, we started getting some more complex expressions in value comparers used in the in-memory database. These expressions became part of the query, which then meant they needed to be translated. Therefore, this logic has been changed to call the value comparer as a method when using the in-memory database, and this method is then detected. This incidentally fixes #27495, which was also a case of a value comparer expression that could not be translated, and any other case where a value comparer could not be translated in in-memory queries.
@ajcvickers ajcvickers changed the title Support more types of value generation with converters Support value generation with converters Apr 4, 2022
ajcvickers added a commit that referenced this issue Apr 5, 2022
Providers may need to be updated to support this.

Fixes #11597
@ajcvickers ajcvickers added providers-beware closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. labels Apr 5, 2022
ajcvickers added a commit that referenced this issue Apr 6, 2022
Providers may need to be updated to support this.

Fixes #11597
@ajcvickers ajcvickers modified the milestones: 7.0.0, 7.0.0-preview4 Apr 18, 2022
@ajcvickers ajcvickers modified the milestones: 7.0.0-preview4, 7.0.0 Nov 5, 2022
@lars-lindstrom
Copy link

Have anyone got this to work on a strongly typed Id using Guids? I can get it to work on Integer types, but not using Guids in the record struct.

public readonly record struct StronglyTypedGuidId
{
    public StronglyTypedGuidId(Guid id)
    {
        Value = id;
    }

    public Guid Value { get; }

    public static StronglyTypedGuidId Create(Guid entityId) => new(entityId);
}

public readonly record struct StronglyTypedIntId
{
    public StronglyTypedIntId(intid)
    {
        Value = id;
    }

    public int Value { get; }

    public static StronglyTypedIntId Create(int entityId) => new(entityId);
}
protected override void OnModelCreating(ModelBuilder builder)
{
     base.OnModelCreating(builder);
     builder.Entity<StronglyTypedIntEntity>().Property(entity=>entity.Id).HasConversion<StronglyTypedIntIdValueConverter>().UseIdentityColumn();
     builder.Entity<StronglyTypedGuidEntity>().Property(entity => entity.Id).HasConversion<StronglyTypedGuidIdValueConverter>().UseIdentityColumn();
}

If I apply both the following errors is produced when building the migration:

If I apply both the following errors is produced when building the migration:
Unable to create a 'DbContext' of type ''. The exception 'Identity value generation cannot be used for the property 'Id' on entity type 'StronglyTypedGuidEntity' because the property type is 'StronglyTypedGuidId'. Identity value generation can only be used with signed integer properties.' was thrown while attempting to create an instance. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728

If I comment out the Guid version, the migration builds and the following is produced:

migrationBuilder.AlterColumn<int>(
    name: "Id",
    schema: "dbo",
    table: "StronglyTypedIntIdEntities",
    type: "int",
    nullable: false,
    oldClrType: typeof(int),
    oldType: "int")
    .Annotation("SqlServer:Identity", "1, 1");

@ilmax
Copy link
Contributor

ilmax commented Jul 26, 2023

I think you shouldn't use .Useidentity with a guid. Also where do you expect the guid to be generated? Db or from the application?

@lars-lindstrom
Copy link

I can be I misunderstand the documentation, but it says Guids can be auto generated by the provider. If this happens on the client (by the provider) or in the Db, doesn't make a difference to me.

Primary keys
By convention, non-composite primary keys of type short, int, long, or Guid are set up to have values generated for inserted entities if a value isn't provided by the application. Your database provider typically takes care of the necessary configuration; for example, a numeric primary key in SQL Server is automatically set up to be an IDENTITY column.

https://learn.microsoft.com/da-dk/ef/core/modeling/generated-properties?tabs=data-annotations#explicitly-configuring-value-generation

@ajcvickers
Copy link
Member Author

@lars-lindstrom You need to use ValueGeneratedOnAdd to let EF know that value of this type should be generated. For example:

modelBuilder
    .Entity<Blog>()
    .Property(entity => entity.Id)
    .ValueGeneratedOnAdd()
    .HasConversion<StronglyTypedGuidIdValueConverter>();

Runnable:

using (var context = new SomeDbContext())
{
    await context.Database.EnsureDeletedAsync();
    await context.Database.EnsureCreatedAsync();

    context.Add(new Blog { Name = "X" });
    context.SaveChanges();
}

using (var context = new SomeDbContext())
{
    var blogs = context.Blogs.ToList();
}

public class SomeDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(@"Data Source=(LocalDb)\MSSQLLocalDB;Database=AllTogetherNow")
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    public DbSet<Blog> Blogs => Set<Blog>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder
            .Entity<Blog>()
            .Property(entity => entity.Id)
            .ValueGeneratedOnAdd()
            .HasConversion<StronglyTypedGuidIdValueConverter>();
    }
}

public class StronglyTypedGuidIdValueConverter : ValueConverter<StronglyTypedGuidId, Guid>
{
    public StronglyTypedGuidIdValueConverter()
        : base(v => v.Value, v => new StronglyTypedGuidId(v))
    {
    }
}

public readonly record struct StronglyTypedGuidId
{
    public StronglyTypedGuidId(Guid id)
    {
        Value = id;
    }

    public Guid Value { get; }

    public static StronglyTypedGuidId Create(Guid entityId) => new(entityId);
}

public class Blog
{
    public StronglyTypedGuidId Id { get; set; }
    public string? Name { get; set; }
}

@lars-lindstrom
Copy link

@ajcvickers Thx.
I believe what confused me, was the fact of no ValueGeneratedOnAdd was pressent in the migration files. After your post I found it in the modelsnapshot. With the risk of being completely mistanken, I suspect autogenerated Guids are not created in the DB, but client side by EF Core. Correct, or am I completely wrong? :-)

Any way, huge thank you. And great job you and rest the team are doing on EF Core.

@ajcvickers
Copy link
Member Author

@lars-lindstrom By default they are generated on the client. This can be changed to server generating using .HasDefaultValueSql to specify the database function to use--usually newsequentialid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-o/c-mapping closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. providers-beware punted-for-3.0 type-enhancement
Projects
None yet