提问人:lonix 提问时间:11/12/2023 最后编辑:lonix 更新时间:11/12/2023 访问量:45
使用对象而不是泛型参数的 EF Core 表达式
EF Core expression with object instead of generic parameter
问:
我有一个搜索实体的方法,并可以选择加载导航属性:
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# 中是不可能的,因为 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()
当然,我上面的例子比你原来的例子更冗长,但通过一些细微的改变,你可以使它简洁:
例如,如果添加新的扩展方法来使用它,然后重命名为 ,然后从头开始传入,则可以跳过调用,最终得到如下结果:Next()
N()
IQueryable<Person>
.Apply
List<Person> list = await dbContext.People.N( p => p.Name ).N( p => p.DateDiscombobulated ).N( p => p.NumnberOfTurkeyTwizzlersDeterged ).ToListAsync();
使用此建议的风险由您自己承担。
评论
上一个:变量自行更改?
评论
Expression<T>
IQueryable<T>
object
Tuple<T0,T1>
Tuple<T0,T1,T2>
TResult
Expression<>
params