通过扩展方法进行子查询过滤
Subquery filtering through extension-method
背景
我正在尝试通过将一些代码提取到一些扩展方法中来清理我的代码,如下所示。我偶然发现了 LINQKit,它具有 Expandable
功能。我创建了一个简单的扩展方法 AtPointInTime
,它将 DateTimeOffset
作为输入(参见下面的代码),但我一直收到错误消息。我做错了什么(无法在文档中找到解决方案)?
实施
[Expandable(nameof(AtPointInTimeImpl))]
public static IQueryable<TSource> AtPointInTime<TSource>(this IQueryable<TSource> query, DateTimeOffset pointInTime) where TSource : class, IBitemporal
{
return query.AsExpandable().Where(entity => AtPointInTimeFilter<TSource>().Invoke(entity, pointInTime));
}
private static Expression<Func<IQueryable<TSource>, DateTimeOffset, IQueryable<TSource>>> AtPointInTimeImpl<TSource>() where TSource : class, IBitemporal
{
return (query, pointInTime) => query.Where(entity => AtPointInTimeFilter<TSource>().Expand().Invoke(entity, pointInTime));
}
private static Expression<Func<TSource, DateTimeOffset, bool>> AtPointInTimeFilter<TSource>() where TSource : class, IBitemporal
{
return (entity, pointInTime) => entity.ValidTimeFrom <= pointInTime && (entity.ValidTimeTo > pointInTime || entity.ValidTimeTo == null);
}
用法
为了获取特定时间点的所有公司,我使用 AtPointInTime
(注意:双时态存储解决方案).
The AtPointInTime
can also be called in a subquery I therefore extended my AtPointInTime
with Expandable
.
示例 #1
var companies = await _dbContext.Companies.AtPointInTime(DateTimeOffset.UtcNow).ToList()
示例 #2
var query = from alarm in _dbContext.Alarms.AtPointInTime(request.PointInTime)
select new GetAlarmQueryResult
{
Alarm = alarm,
Company = _dbContext.Companies.AtPointInTime(alarm.Created).SingleOrDefault(),
};
错误
The LINQ expression 'DbSet<Company>
.Where(c => (entity, pointInTime) => (DateTimeOffset)entity.ValidTimeFrom <= pointInTime && (Nullable<DateTimeOffset>)entity.ValidTimeTo > (Nullable<DateTimeOffset>)pointInTime || entity.ValidTimeTo == null
.Invoke(
expr: c,
arg1: __pointInTime_0))' 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 either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
要消除的几件事:
这个表达式:
return (entity, pointInTime) => entity.ValidTimeFrom <= pointInTime && (entity.ValidTimeTo > pointInTime || entity.ValidTimeTo == null);
应该是:
return (entity, pointInTime) => entity.ValidTimeFrom <= pointInTime && (entity.ValidTimeTo == null || entity.ValidTimeTo > pointInTime);
反过来,它会在 null 检查之前的比较中评估可为 null 的 属性。 EF 可能能够生成它,但也可能不会。
在我使用 AsExpandable
的地方,它一直在 DbSet
,而不是 IQueryable
。尝试将 AsExpandable 移出扩展方法并调用:
var companies = await _dbContext.Companies
.AsExpandable()
.AtPointInTime(DateTimeOffset.UtcNow)
.ToListAsync()
如果可行,可能需要更新扩展方法以针对 DbSet<TEntity>
而不是 IQueryable<TEntity>
。我怀疑 _dbContext.Companies.AsQueryable().AtPointInTime(...)
是否可行,尽管它可能值得一试? :)
另外,它看起来 应该 用于包装临时检查。如果我不得不冒险猜测扩展中的 AsExpandable() 调用而不是直接在 DbSet 上调用意味着 AsExpandable
没有在表达式构建器中“接受”,或者倒置条件可能被传递为 -是和塞满了一代人。
我假设 Plain old manual linq expression 工作得很好?
var pointInTime = DateTimeOffset.UtcNow;
var companies = await _dbContext.Companies
.Where(entity => entity.ValidTimeFrom <= pointInTime
&& (entity.ValidTimeTo == null
|| entity.ValidTimeTo > pointInTime)
.ToListAsync();
尝试以下实施:
[Expandable(nameof(AtPointInTimeImpl))]
public static IQueryable<TSource> AtPointInTime<TSource>(this IQueryable<TSource> query, DateTimeOffset pointInTime)
where TSource : class, IBitemporal
{
return query.AsExpandable().Where(entity => entity.AtPointInTime(pointInTime));
}
private static Expression<Func<IQueryable<TSource>, DateTimeOffset, IQueryable<TSource>>> AtPointInTimeImpl<TSource>()
where TSource : class, IBitemporal
{
return (query, pointInTime) => query.Where(entity => entity.AtPointInTime(entity, pointInTime));
}
[Expandable(nameof(AtPointInTimeEntityImpl))]
public static bool AtPointInTime<TSource>(this TSource entity, DateTimeOffset pointInTime)
where TSource : class, IBitemporal
{
throw new NotImplementedException();
}
private static Expression<Func<TSource, DateTimeOffset, bool>> AtPointInTimeEntityImpl<TSource>()
where TSource : class, IBitemporal
{
return (entity, pointInTime) => entity.ValidTimeFrom <= pointInTime && (entity.ValidTimeTo > pointInTime || entity.ValidTimeTo == null);
}
如果配置 EF Core 选项也可以消除 AsExpandable
:
builder
.UseSqlServer(connectionString)
.WithExpressionExpanding(); // enabling LINQKit extension
背景
我正在尝试通过将一些代码提取到一些扩展方法中来清理我的代码,如下所示。我偶然发现了 LINQKit,它具有 Expandable
功能。我创建了一个简单的扩展方法 AtPointInTime
,它将 DateTimeOffset
作为输入(参见下面的代码),但我一直收到错误消息。我做错了什么(无法在文档中找到解决方案)?
实施
[Expandable(nameof(AtPointInTimeImpl))]
public static IQueryable<TSource> AtPointInTime<TSource>(this IQueryable<TSource> query, DateTimeOffset pointInTime) where TSource : class, IBitemporal
{
return query.AsExpandable().Where(entity => AtPointInTimeFilter<TSource>().Invoke(entity, pointInTime));
}
private static Expression<Func<IQueryable<TSource>, DateTimeOffset, IQueryable<TSource>>> AtPointInTimeImpl<TSource>() where TSource : class, IBitemporal
{
return (query, pointInTime) => query.Where(entity => AtPointInTimeFilter<TSource>().Expand().Invoke(entity, pointInTime));
}
private static Expression<Func<TSource, DateTimeOffset, bool>> AtPointInTimeFilter<TSource>() where TSource : class, IBitemporal
{
return (entity, pointInTime) => entity.ValidTimeFrom <= pointInTime && (entity.ValidTimeTo > pointInTime || entity.ValidTimeTo == null);
}
用法
为了获取特定时间点的所有公司,我使用 AtPointInTime
(注意:双时态存储解决方案).
The
AtPointInTime
can also be called in a subquery I therefore extended myAtPointInTime
withExpandable
.
示例 #1
var companies = await _dbContext.Companies.AtPointInTime(DateTimeOffset.UtcNow).ToList()
示例 #2
var query = from alarm in _dbContext.Alarms.AtPointInTime(request.PointInTime)
select new GetAlarmQueryResult
{
Alarm = alarm,
Company = _dbContext.Companies.AtPointInTime(alarm.Created).SingleOrDefault(),
};
错误
The LINQ expression 'DbSet<Company>
.Where(c => (entity, pointInTime) => (DateTimeOffset)entity.ValidTimeFrom <= pointInTime && (Nullable<DateTimeOffset>)entity.ValidTimeTo > (Nullable<DateTimeOffset>)pointInTime || entity.ValidTimeTo == null
.Invoke(
expr: c,
arg1: __pointInTime_0))' 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 either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
要消除的几件事:
这个表达式:
return (entity, pointInTime) => entity.ValidTimeFrom <= pointInTime && (entity.ValidTimeTo > pointInTime || entity.ValidTimeTo == null);
应该是:
return (entity, pointInTime) => entity.ValidTimeFrom <= pointInTime && (entity.ValidTimeTo == null || entity.ValidTimeTo > pointInTime);
反过来,它会在 null 检查之前的比较中评估可为 null 的 属性。 EF 可能能够生成它,但也可能不会。
在我使用 AsExpandable
的地方,它一直在 DbSet
,而不是 IQueryable
。尝试将 AsExpandable 移出扩展方法并调用:
var companies = await _dbContext.Companies
.AsExpandable()
.AtPointInTime(DateTimeOffset.UtcNow)
.ToListAsync()
如果可行,可能需要更新扩展方法以针对 DbSet<TEntity>
而不是 IQueryable<TEntity>
。我怀疑 _dbContext.Companies.AsQueryable().AtPointInTime(...)
是否可行,尽管它可能值得一试? :)
另外,它看起来 应该 用于包装临时检查。如果我不得不冒险猜测扩展中的 AsExpandable() 调用而不是直接在 DbSet 上调用意味着 AsExpandable
没有在表达式构建器中“接受”,或者倒置条件可能被传递为 -是和塞满了一代人。
我假设 Plain old manual linq expression 工作得很好?
var pointInTime = DateTimeOffset.UtcNow;
var companies = await _dbContext.Companies
.Where(entity => entity.ValidTimeFrom <= pointInTime
&& (entity.ValidTimeTo == null
|| entity.ValidTimeTo > pointInTime)
.ToListAsync();
尝试以下实施:
[Expandable(nameof(AtPointInTimeImpl))]
public static IQueryable<TSource> AtPointInTime<TSource>(this IQueryable<TSource> query, DateTimeOffset pointInTime)
where TSource : class, IBitemporal
{
return query.AsExpandable().Where(entity => entity.AtPointInTime(pointInTime));
}
private static Expression<Func<IQueryable<TSource>, DateTimeOffset, IQueryable<TSource>>> AtPointInTimeImpl<TSource>()
where TSource : class, IBitemporal
{
return (query, pointInTime) => query.Where(entity => entity.AtPointInTime(entity, pointInTime));
}
[Expandable(nameof(AtPointInTimeEntityImpl))]
public static bool AtPointInTime<TSource>(this TSource entity, DateTimeOffset pointInTime)
where TSource : class, IBitemporal
{
throw new NotImplementedException();
}
private static Expression<Func<TSource, DateTimeOffset, bool>> AtPointInTimeEntityImpl<TSource>()
where TSource : class, IBitemporal
{
return (entity, pointInTime) => entity.ValidTimeFrom <= pointInTime && (entity.ValidTimeTo > pointInTime || entity.ValidTimeTo == null);
}
如果配置 EF Core 选项也可以消除 AsExpandable
:
builder
.UseSqlServer(connectionString)
.WithExpressionExpanding(); // enabling LINQKit extension