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

Using ValueOf with Entity Framework Core? #28

Open
jyscao opened this issue Apr 3, 2022 · 8 comments
Open

Using ValueOf with Entity Framework Core? #28

jyscao opened this issue Apr 3, 2022 · 8 comments

Comments

@jyscao
Copy link

jyscao commented Apr 3, 2022

Hi, I'm trying to incorporate your excellent library into my EFCore model classes, for example:

    public partial class Account
    {
        public Account()
        {
            WorkerGroups = new HashSet<WorkerGroup>();
        }

        public LongId Id { get; set; }
        public DateTime? Created { get; set; }
        public DateTime? AgreedToTermsOn { get; set; }
        public NameLabel Name { get; set; }
        public GuidStr ApiKey { get; set; }
        public string CustomUrl { get; set; }

        public virtual ICollection<WorkerGroup> WorkerGroups { get; set; }
    }

Where LongId, NameLabel, GuidStr have C# primitive types of long, string, string respectively, with appropriate validation checks (e.g. under a certain length for NameLabel, and of certain format for GuidStr).

The code compiles fine, however when I actually try to fetch one of the above record from the database, I'm running into this error: System.InvalidOperationException: The property 'Account.Id' is of type 'LongId' which is not supported by the current database provider. Either change the property CLR type, or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

I also tried to annotate the model with something like [Column(TypeName = "bigint")] on the Id property for example, and it didn't do anything. So I just wanted to ask you if what I'm trying to do is reasonable and/or feasible. Maybe I should just return to using primitive C# types?

@chucker
Copy link

chucker commented Apr 3, 2022

I'm not that experienced with EF, but maybe this will work?

[Column(TypeName = "int, not null")] // explicitly map the SQL data type to `int`
public LongId Id { get; set; }

Failing something like that, you're probably better off using something like AutoMapper to separate your entities (classes that map 1:1 to fields in your database and are only used in code that directly interacts with the DB) and your models (classes that use ValueOf).

@SteveDunn
Copy link

Vogen is similar to this but it has certain opinions/constraints on safety (for instance, not being able to create default instances of value objects). It's also focused on speed; there's almost no overheard compared to using a primitive directly.
It's a source generator, and can generate code for EF (as well as Json and Dapper).
I'm not after stealing customers away from this very useful little package, but you might find it has what you need (if you're happy with the constraints it imposes)

@jyscao
Copy link
Author

jyscao commented Apr 4, 2022

@chucker tried your suggestion just now, same error as before. In fact even when I annotate the property with [NotMapped], the same error still pops up. So I get the feeling that I haven't even been addressing the issue at the right level. To me EFCore definitely contains enough magic that I'm not even sure where to really start looking into this. Thanks anyway for trying to help.

@jyscao
Copy link
Author

jyscao commented Apr 4, 2022

@SteveDunn thanks for the tip, for the time being I've switched back to working in primitive obsession mode just because I want to make progress on the actual code I gotta get done (it's for work unfortunately). But I'll keep your package in mind for when I do the refactoring down the line, hopefully sooner rather than later.

@mcintyre321
Copy link
Owner

I haven't really used EF for a while, but I suspect you need a custom ValueConverter https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions?tabs=data-annotations

Another option might be https://docs.microsoft.com/en-us/ef/core/modeling/backing-field?tabs=data-annotations

@ttlaare
Copy link

ttlaare commented Jun 1, 2022

Using value conversions with ValueOf works perfectly fine.

public class CustomerContext : DbContext
{
    public CustomerContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<Customer> Customers => Set<Customer>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder
            .Entity<Customer>()
            .Property(e => e.Id)
            .HasConversion(
                convertToProviderExpression: id => id.Value,
                convertFromProviderExpression: id => CustomerId.From(id));

        modelBuilder
            .Entity<Customer>()
            .Property(e => e.Email)
            .HasConversion(
                convertToProviderExpression: emailAddress => emailAddress.Value,
                convertFromProviderExpression: emailAddress => EmailAddress.From(emailAddress));
    }
}

Check out my repository for a fully working example:
https://github.com/ttlaare/ValueOfWithEf

@jlauwers
Copy link

jlauwers commented Jun 3, 2022

If you want a generic ValueOfConverter:

public class ValueOfConverter<T, TType> : ValueConverter<T, TType>
    where T : ValueOf<TType, T>, new()
{
    public ValueOfConverter() : base(
        id => id.Value,
        id => ValueOf<TType, T>.From(id))
    {
    }
}

builder.Property(x => x.Foobar).HasConversion<ValueOfConverter<Foobar, int>>();

@AdamJachocki
Copy link

I am reopening the issue. I tried, but I'm not able to map tuple value to EfCore. Like:

public class Address : ValueOf<(string Street, string StreetNumber, string UnitNumber, string City, string PostalCode, string Country), Address>

And I want it to be OwnedBy entity or set as ComplexProperty. But everything I tried doesn't work. So, is ValueOf rather for single primitives than whole structs?

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

No branches or pull requests

7 participants