使用对象而不是泛型参数的 EF Core 表达式

EF Core expression with object instead of generic parameter

提问人:lonix 提问时间:11/12/2023 最后编辑:lonix 更新时间:11/12/2023 访问量:45

问:

我有一个搜索实体的方法,并可以选择加载导航属性:

public async Task<Person?> FindPerson<TProperty>(string name, params Expression<Func<Person, IEnumerable<TProperty>>>[] includes) where TProperty : class
{
  var person = await Find(name);
  if (person != null)
  {
    foreach (var include in includes)
      await _context.Entry(person).Collection(include).LoadAsync();
  }
}

(请注意,这是我的真实代码的一个非常简化的版本;内容无关紧要,方法签名是问题所在。

我希望调用站点是:

var person = await FindPerson("Mike");
// or
var person = await FindPerson("Mike", x => x.Account);

但是第一个不起作用,因为我没有指定(“无法从用法中推断出类型参数”)。<TProperty>

解决方法是以下签名:

public async Task<Person?> FindPerson(string name, params Expression<Func<Person, IEnumerable<object>>>[] includes)

将泛型参数 TProperty 更改为类型对象有什么影响?假设它在这种狭隘的情况下有效,它会在我没有遇到过的其他情况下爆炸吗?还是我应该考虑性能问题?

C# LINQ 泛型 .net-core 实体框架核心

评论

0赞 lonix 11/12/2023
另一种(安全的)解决方法是有两个重载,但在我的情况下,它引入了一些重复,如果可能的话,我想避免。
0赞 Dai 11/12/2023
你为什么不使用你的对象来构建一个,然后只执行一次呢?相反,您的代码似乎对每个参数执行查询 - 这似乎很慢且浪费......Expression<T>IQueryable<T>
0赞 lonix 11/12/2023
@Dai我同意,你的方式更好......但该代码不是真实的,它只是要放入方法主体中的东西。这是我感兴趣的签名。我通常有兴趣了解在这种情况下使用的缺点。object
0赞 Dai 11/12/2023
好吧,我想我现在明白你的意思了。所以,你所问的问题在今天的 C# 中是不可能的,因为 C# 不支持可变泛型类型(或任何类型的高级类型,真的)(这就是为什么 .NET 有 12+ 个不同版本的 , , 等。因为这些参数中的每一个都可能是不同的类型,所以你需要放弃使用的梦想,而满足于某种链式的通用结构......Tuple<T0,T1>Tuple<T0,T1,T2>TResultExpression<>params
0赞 lonix 11/12/2023
@Dai 快速搜索“链式通用结构”的 SO 没有产生任何见解......什么?关于元组类比,是的,这就是我所做的(实现了许多重载)。

答:

1赞 Dai 11/12/2023 #1

从我的评论:

好吧,我想我现在明白你的意思了。所以,你所问的问题在今天的 C# 中是不可能的,因为 C# 不支持可变泛型类型(或任何类型的高级类型,真的)(这就是为什么 .NET 有 12+ 个不同版本的 , , 等。因为每个 Expression<> 参数的 TResult 可能是不同的类型,所以你需要放弃使用 params 的梦想,而满足于某种链式泛型结构......Tuple<T0,T1>Tuple<T0,T1,T2>

我的意思是这样的:

public interface IThing<TEntity>
    where TEntity : class
{
    IQueryable<TEntity> Apply( IQueryable<TEntity> query );
}

public readonly struct Thing<TEntity, TProperty, TPrev > : IThing<TEntity>
    where TEntity : class
    where TPrev : struct, IThing<TEntity>
{
    private readonly TPrev                               prev;
    private readonly Expression<Func<TEntity,TProperty>> expr;
    
    public Thing( TPrev prev, Expression<Func<TEntity,TProperty>> expr )
    {
        this.prev = prev;
        this.expr = expr;
    }
    
    public Thing< TEntity, TNextProperty, /*TPrev:*/ Thing<TEntity,TProperty,TPrev> > Next<TNextProperty>( Expression<Func<TEntity,TNextProperty>> expr )
    {
        return new Thing<TEntity, TNextProperty, Thing<TEntity,TProperty,TPrev> >( this, expr );
    }
    
    public IQueryable<TEntity> Apply( IQueryable<TEntity> query )
    {
        query = EntityFrameworkQueryableExtensions.Include( source: query, navigationPropertyPath: this.expr );
        
        return this.prev.Apply( query );
    }
}

public readonly struct Start<TEntity> : IThing<TEntity>
    where TEntity : class
{
    public Thing< TEntity, TNextProperty, /*TPrev:*/ Start<TEntity> > Next<TNextProperty>( Expression<Func<TEntity,TNextProperty>> expr )
    {
        return new Thing<TEntity, TNextProperty, Start<TEntity> >( this, expr );
    }
    
    public IQueryable<TEntity> Apply( IQueryable<TEntity> query )
    {
        // Base case: just return directly.
        return query;
    }
}

可以这样使用:

public class Person
{
    public String Name { get; }
    
    public DateOnly DateDiscombobulated { get; }
    
    public Int32 NumnberOfTurkeyTwizzlersDeterged { get; }
    
    //
    
    public Family Family { get; }
    
    public IList<DustBunny> SecretShames { get; }
}

public class Family
{
}

public class DustBunny
{
}

//

IQueryable<Person> query = dbContext.People;
    
IQueryable<Person> mutatedQuery = new Start<Person>()
    .Next( p => p.Name )
    .Next( p => p.DateDiscombobulated )
    .Next( p => p.NumnberOfTurkeyTwizzlersDeterged )
    .Apply( query );

Proptip:将其加载到编辑器中,并查看上次调用结果的推断类型:保留了 100% 的泛型类型信息。通过链接使用递归泛型是目前在 C# 中可以完成的最接近可变参数泛型的事情。.Next()

enter image description here


当然,我上面的例子比你原来的例子更冗长,但通过一些细微的改变,你可以使它简洁:

例如,如果添加新的扩展方法来使用它,然后重命名为 ,然后从头开始传入,则可以跳过调用,最终得到如下结果:Next()N()IQueryable<Person>.Apply

List<Person> list = await dbContext.People.N( p => p.Name ).N( p => p.DateDiscombobulated ).N( p => p.NumnberOfTurkeyTwizzlersDeterged ).ToListAsync();

使用此建议的风险由您自己承担。

评论

1赞 lonix 11/12/2023
哇!你也喜欢威慑火鸡搅拌器?!我以为我是唯一一个!
0赞 lonix 11/12/2023
需要时间来消化......谢谢。