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

A problem with owned Type and SetValueConverter at runtime. #25532

Closed
yangzhongke opened this issue Aug 16, 2021 · 3 comments
Closed

A problem with owned Type and SetValueConverter at runtime. #25532

yangzhongke opened this issue Aug 16, 2021 · 3 comments

Comments

@yangzhongke
Copy link

yangzhongke commented Aug 16, 2021

I tried to implement Strongly-Typed-Id in EF Core.
The following code works well:

public abstract record StronglyTypedId
{
	public Guid Value { get; init; }
	public StronglyTypedId()
	{
		this.Value = Guid.NewGuid();
	}
	public StronglyTypedId(Guid id)
	{
		this.Value = id;
	}
}
public record CategoryId : StronglyTypedId
{
	public CategoryId()
	{

	}

	public CategoryId(Guid id) : base(id)
	{

	}
}

public class Category : BaseEntity, IAggregateRoot, ISoftDelete
{
	private Category() { }
	public CategoryId Id { get; set; }
	public int SequenceNumber { get; private set; }
	public string Name { get; private set; }
}		

public class CategoryConfig : IEntityTypeConfiguration<Category>
{
	public void Configure(EntityTypeBuilder<Category> builder)
	{
		builder.HasKey(p => p.Id);
		builder.Property(p => p.Id)
			   .HasConversion(id => id.Value, value => new CategoryId(value));
	}
}	

However, when I tried to set valueconverters for every Strongly-Typed-Id properties at runtime, the code is as following:
Step one: Remove "builder.Property(p => p.Id).HasConversion(id => id.Value, value => new CategoryId(value));" from CategoryConfig
Step Two:

public static class StronglyTypedIdHelper
{
	public static void AddStronglyTypedIdConversions(this ModelBuilder modelBuilder)
	{
		foreach (var entityType in modelBuilder.Model.GetEntityTypes())
		{
			if (entityType.IsOwned())
			{
				continue;
			}
			foreach (var property in entityType.GetProperties())
			{
				Type propertyType = property.ClrType;
				if (IsStronglyTypedId(propertyType))
				{                        
					Type valueType = typeof(Guid);
					var converter = StronglyTypedIdConverters.GetOrAdd(
						propertyType,
						_ => CreateStronglyTypedIdConverter(propertyType, valueType));
					property.GetValueConverter();
					property.SetValueConverter(converter);
				}
			}
		}
	}

	private static bool IsStronglyTypedId(Type type)
	{
	   return type.IsAssignableTo(typeof(StronglyTypedId));
	}

	private static readonly ConcurrentDictionary<Type, ValueConverter> StronglyTypedIdConverters = new();

	private static ValueConverter CreateStronglyTypedIdConverter(
		Type stronglyTypedIdType,
		Type valueType)
	{
		// id => id.Value
		var toProviderFuncType = typeof(Func<,>)
			.MakeGenericType(stronglyTypedIdType, valueType);
		var stronglyTypedIdParam = Expression.Parameter(stronglyTypedIdType, "id");
		var toProviderExpression = Expression.Lambda(
			toProviderFuncType,
			Expression.Property(stronglyTypedIdParam, "Value"),
			stronglyTypedIdParam);

		// value => new ProductId(value)
		var fromProviderFuncType = typeof(Func<,>)
			.MakeGenericType(valueType, stronglyTypedIdType);
		var valueParam = Expression.Parameter(valueType, "value");
		var ctor = stronglyTypedIdType.GetConstructor(new[] { valueType });
		var fromProviderExpression = Expression.Lambda(
			fromProviderFuncType,
			Expression.New(ctor, valueParam),
			valueParam);

		var converterType = typeof(ValueConverter<,>)
			.MakeGenericType(stronglyTypedIdType, valueType);

		return (ValueConverter)Activator.CreateInstance(
			converterType,
			toProviderExpression,
			fromProviderExpression,
			null);
	}
}

Step Three: invoke AddStronglyTypedIdConversions in OnModelCreating

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
           modelBuilder.AddStronglyTypedIdConversions();
        }

The code above cannot work properly, and exception 'The entity type XXXId requires a primary key to be defined.' will be thrown.
Because entityType.GetProperties() only returns scalar properties, the Id property of Category is not included in the returned values of entityType.GetProperties(). I can get the Id property of Category with entityType.GetNavigations(), but IMutableNavigation cannot invoke the SetValueConverter method.

.NET Version:.NET 5
EF Core version:5.0.9

My questioin is :how can I set ValueConverter for non-scalar property at runtime?
Thanks.

@yangzhongke yangzhongke changed the title A bug of EF Core with owned Type and SetValueConverter at runtime. A problem with owned Type and SetValueConverter at runtime. Aug 16, 2021
@AndriySvyryd
Copy link
Member

Duplicate of #13947

@AndriySvyryd
Copy link
Member

AndriySvyryd commented Aug 17, 2021

In 6.0.0-preview6 we shipped #10784 which allows you to call .Properties<CategoryId>().HaveConversion<CategoryIdConverter>()

@yangzhongke
Copy link
Author

yangzhongke commented Aug 19, 2021

In 6.0.0-preview6 we shipped #10784 which allows you to call .Properties<CategoryId>().HaveConversion<CategoryIdConverter>()

Thanks for your replies. However, "Properties().HaveConversion()" cannot solve my problem, because what I want to do is to SetValueConverter at runtime.

foreach (var navigation in entityType.GetNavigations())
{
	Type propertyType = navigation.ClrType;
	if (IsStronglyTypedId(propertyType))
	{
		Type valueType = typeof(Guid);
		var converter = StronglyTypedIdConverters.GetOrAdd(
			propertyType,
			_ => CreateStronglyTypedIdConverter(propertyType, valueType));
		navigation.SetValueConverter(converter);//error: no such method
	}
}

@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants