andrewlock / StronglyTypedId

A Rosyln-powered generator for strongly-typed IDs
MIT License
1.52k stars 79 forks source link

add docs for integration with entity framework and dapper #8

Open chinwobble opened 5 years ago

chinwobble commented 5 years ago

Great project!

Would you be welcome to adding more documentation on how to integrate this with entity framework and dapper so that they get mapped properly to the DB?

chinwobble commented 4 years ago

@andrewlock anyway this is possible?

andrewlock commented 4 years ago

Hi @chinwobble, sorry for the delay! You can use a Dapper TypeHandler<> as shown in this post. For EF Core, you can use the approach from this post I haven't had a chance to see if it works with EF Core 3 yet!

jedielson commented 2 years ago

Hi... Is there any plan to do it?

In the new versions we are able to add StronglyTypedIdConverter.EfCoreValueConverter to our strongly typed value, but it forces us to reference EF Core in same project of our models.

How can we use it with fluent Api? Do I have to add EF as a reference for my models project?

P.S: Awesome lib. Congrats 😄

MovGP0 commented 2 years ago

I made some improvements on that value converter selector, such that it also works with non-GUID ids:

using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using StronglyTypedIds;
using System.Collections.Concurrent;

namespace StronglyTypedId;

public sealed class StronglyTypedIdValueConverterSelector: ValueConverterSelector
{
    private readonly ConcurrentDictionary<(Type ModelClrType, Type ProviderClrType), ValueConverterInfo> _converters = new();

    public StronglyTypedIdValueConverterSelector(ValueConverterSelectorDependencies dependencies) : base(dependencies)
    { }

    public override IEnumerable<ValueConverterInfo> Select(Type modelClrType, Type? providerClrType = null)
    {
        var baseConverters = base.Select(modelClrType, providerClrType);
        foreach (var converter in baseConverters)
        {
            yield return converter;
        }

        // Extract the "real" type T from Nullable<T> if required
        var underlyingModelType = UnwrapNullableType(modelClrType);
        var underlyingProviderType = UnwrapNullableType(providerClrType);

        // 'null' means 'get any value converters for the modelClrType'
        if (underlyingProviderType is not null && underlyingProviderType != typeof(Guid)) yield break;

        // Try and get a nested class with the expected name.
        var converterType = underlyingModelType?.GetNestedType(nameof(StronglyTypedIdConverter.EfCoreValueConverter));

        if (underlyingModelType == null) yield break;
        if (converterType == null) yield break;

        var baseType = converterType!.BaseType!.GenericTypeArguments[1]!;
        yield return _converters.GetOrAdd((underlyingModelType, baseType), _ =>
        {
            // Create an instance of the converter whenever it's requested.
            ValueConverter factory(ValueConverterInfo info) => (ValueConverter)Activator.CreateInstance(converterType, info.MappingHints)!;

            // Build the info for our strongly-typed ID => Guid converter
            return new ValueConverterInfo(modelClrType, baseType, factory);
        });
    }

    private static Type? UnwrapNullableType(Type? type)
    {
        if (type is null) { return null; }
        return Nullable.GetUnderlyingType(type) ?? type;
    }
}

I still have a problem with linq queries that join a strongly typed id with the unterlying type.

Example

Assume we have the following classes:

public abstract class Person<T> where T:struct
{
    public T Id { get; set; }
    public string FirstName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;
}

public sealed class Customer : Person<CustomerId>
{
}

public sealed class Employee : Person<EmployeeId>
{
}

public sealed class PersonInfo
{
    // could be a CustomerId or a PersonId, so we use Guid here
    public Guid PersonId { get; set; }
    public string EmailAddress  { get; set; } = string.Empty;
}

The following query causes an exception:

from c in ctx.Customer
join pi in ctx.PersonInfo on c.Id.Value equals pi.PersonId
select new {
    c.FirstName,
    c.LastName,
    pi.EmailAddress
}

This causes the same error:

from c in ctx.Customer
join pi in ctx.PersonInfo on c.Id.ToString() equals pi.PersonId.ToString()
select new {
    c.FirstName,
    c.LastName,
    pi.EmailAddress
}
The LINQ expression '...' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
mohaaron commented 10 months ago

Hi... Is there any plan to do it?

In the new versions we are able to add StronglyTypedIdConverter.EfCoreValueConverter to our strongly typed value, but it forces us to reference EF Core in same project of our models.

How can we use it with fluent Api? Do I have to add EF as a reference for my models project?

P.S: Awesome lib. Congrats 😄

I'm just adding this package for the first time and trying it out, and this question about needing to reference the EFCore is what I'm wondering about.