首页 > 解决方案 > 实体框架 Linq 查询在使用带有 ValueConverter 的自定义类型时由客户端评估

问题描述

我正在使用 Microsoft 的Entity Framework Core并尝试利用ValueConverters来允许我的数据库模型实体中的自定义类型。关键是要有我自己的类型,我可以自定义它并将其余代码与数据库中实际使用的类型隔离开来。

(遗憾的是,遗留代码直接访问模型实体而没有接口,所以这是我剩下的,除非我进行重大检修。)

它主要工作,但我的问题是实体框架无法将我的类型转换为数据库类型的where 子句 (可能是其他,但这是我遇到的),而是进行客户端评估,这显然是性能问题,因为所有候选人都被查询。

所以,我想知道是否有人遇到过这种情况,是否有解决方案,或者我是否必须尝试不同的方法。


如果你想要一些代码,它就在那里。我试图把它修整,所以实现有点奇怪,但它仍然以同样的方式失败。

让我们调用我的自定义结构类型ItemId,使其包含一个字符串并允许从 long 或 string 创建它:

public struct ItemId
{
    public string Data;

    public ItemId(long data)
    {
        Data = data.ToString();
    }

    public ItemId(string data)
    {
        Data = data;
    }

    public override bool Equals(object obj)
    {
        return obj is ItemId itemId && Data == itemId.Data;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Data);
    }

    public static bool operator ==(ItemId id1, ItemId id2)
    {
        return id1.Data == id1.Data;
    }

    public static bool operator !=(ItemId id1, ItemId id2)
    {
        return !(id1== id1);
    }
}

然后,有一个用于存储 64 位数字 ID 的数据库的转换器。我强烈怀疑手写表达式是不必要的,因为内置转换器通常不使用它们并且它们似乎工作正常,但我添加了它们以试图解决我的问题:

public class ItemIdToLongConverter : ValueConverter<ItemId, long>
{
    public ItemIdToLongConverter(ConverterMappingHints mappingHints = null)
        : base(ToLong(), ToItemId(), mappingHints)
    { }

   protected static Expression<Func<ItemId, long>> ToLong()
    {
        var data = typeof(ItemId).GetField(nameof(ItemId.StringData));
        var tryParseMethod = typeof(long).GetMethod(
            nameof(long.TryParse),
            new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(long).MakeByRefType() });

        var param = Expression.Parameter(typeof(ItemId));
        var parsedVariable = Expression.Variable(typeof(long));

        return Expression.Lambda<Func<ItemId, long>>(
            Expression.Block(
                typeof(long),
                new[] { parsedVariable },
                Expression.Condition(
                    Expression.Call(
                        tryParseMethod,
                        Expression.Field(param, data),
                        Expression.Constant(NumberStyles.Any),
                        Expression.Constant(CultureInfo.InvariantCulture, typeof(IFormatProvider)),
                        parsedVariable),
                    parsedVariable,
                    Expression.Constant(default(long), typeof(long)))),
            param);
    }

    protected static Expression<Func<long, ItemId>> ToItemId()
    {
        var ctor = typeof(ItemId).GetConstructor(new[] { typeof(long) });

        var param = Expression.Parameter(typeof(long));

        return Expression.Lambda<Func<long, ItemId>>(
            Expression.Block(
                typeof(ItemId),
                Expression.New(ctor, param)
            ),
            param);
    }
}

我以这种方式在模型中注册我的转换器:

modelBuilder.Entity<MyTable>(entity =>
{
    ...
    entity.Property(e => e.ItemId).HasConversion(new ItemIdToLongConverter()).ValueGeneratedNever();
    ...
});

这是一个得到客户端评估的查询,因为它不能转换id为数据库类型:

var id = new ItemId(100);
dbContext.MyTable.FirstOrDefault(x => x.ItemId == id);

奇怪的是,这个奇怪的结构翻译得很好:

var ids = Enumerable.Repeat(new ItemId(100), 1);
dbContext.MyTable.FirstOrDefault(x => ids.Contains(x.ItemId));

标签: c#linq-to-sqlentity-framework-corevalueconverter

解决方案


推荐阅读