为什么 Entity Framework 在使用 Linq .Union() 或 .Except() 时生成错误的列名?
Why Entity Framework generates bad column names when using Linq .Union() or .Except()?
我在我的项目中使用 EF 6。我只想从数据库中获取一些数据,有时加入额外的数据。
这是我的代码:
var queryable = dbContext.Manuals
.AsNoTracking()
.Where(x => x.Status == "ACTIVE");
if (showDisabledParents)
{
var parentsIds = queryable
.Where(x => x.ParentId.HasValue)
.Select(x => x.ParentId.Value)
.Distinct()
.ToArray();
queryable = queryable.Union(
dbContext.Manuals
.AsNoTracking()
.Where(x => parentsIds.Contains(x.Id))
);
}
当 showDisabledParents 为 false 时效果很好。 EF 将生成具有正确列名的 SQL 查询。
但是当 showDisabledParents 为 true 时,EF 生成 UNION 语句(这是预期的)但它使用 C1、C2、C3 .. . 对于列名。
问题是我有一个自定义 DbDataReader,它调用 DateTime.SpecifyKind(..., DateTimeKind.Utc) 如果列名以 "Utc" 结尾。由于 EF 使用了错误的列名(C1、C2、C3 等),我的逻辑无法正常工作。
是否可以通过某种方式阻止这种行为?我的意思是,如果有办法告诉 EF 不要使用这些奇怪的列名。
更新:
这是我的 DBDataReaderCode:
public class MyDbDataReader : DelegatingDbDataReader
{
private string _dbName;
public MyDbDataReader(string dbName, DbDataReader source)
: base(source)
{
_dbName = dbName;
}
public override DateTime GetDateTime(int ordinal)
{
return DateTime.SpecifyKind(base.GetDateTime(ordinal), base.GetName(ordinal).EndsWith("UTC", StringComparison.OrdinalIgnoreCase)
? DateTimeKind.Utc
: DateTimeKind.Local);
}
}
我认为这是不可能的,因为 EF 使用内部别名来生成整个查询,这样可以确保从不同表等收集的名称中没有重复项。
无论如何,我宁愿搜索有关您处理日期的问题。您能否提供有关您的案例的更多详细信息?一般来说,经验法则是始终以 UTC 格式保留日期并根据需要转换为特定时区。
我用于处理 DateTime.Kind 的典型方法是在适用的实体 属性 上使用属性。例如,这将在任何 UTC:
的 DateTime 实体 属性 上进行
[DateTimeKind(DateTimeKind.Utc)]
public DateTime SomeDateUTC { get; set; }
如果您希望将其他日期时间标记为当地时间:
[DateTimeKind(DateTimeKind.Local)]
public DateTime SomeDate { get; set; }
没有属性的 DateTimes 将保留为未指定类型。
属性本身:参见 (Entity Framework DateTime and UTC)
然后在您的 DbContext 中,您只需将其添加到 InitializeContext 方法中:
((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
(sender, e) => DateTimeKindAttribute.Apply(e.Entity);
否则,如果你想坚持你现在的方法,而且你依赖于列名,Union
不能指望,所以将条件拆分为两个查询。这将需要在执行查询的任何地方完成,因此如果您的方法返回 IQueryable<Manual>
,则需要将其更改为 IEnumerable<IQueryable<Manual>>
,如果返回多个查询,则合并结果。
var queryables = new List<IQueryable<Manual>();
queryables.Add(dbContext.Manuals
.AsNoTracking()
.Where(x => x.Status == "ACTIVE"));
if (showDisabledParents)
{
var parentsIds = dbContext.Manuals
.Where(x => x.Status == "ACTIVE"
&& x.ParentId.HasValue)
.Select(x => x.ParentId.Value)
.Distinct()
.ToArray();
queryables.Add = dbContext.Manuals
.AsNoTracking()
.Where(x => parentsIds.Contains(x.Id));
}
return queryables;
如果您改为在此方法中执行查询,则只需捕获第二个查询(如果需要)并合并要返回的结果。
这里最棘手的一点是,如果您需要对组合集进行分页。在这种情况下,我会改为使用 two-pass 方法,在适当排序后获取相关任务 ID,然后对 ID 使用分页提取,按 ID 加载实体:
var queryable = dbContext.Manuals
.AsNoTracking()
.Where(x => x.Status == "ACTIVE");
if (showDisabledParents)
{
var parentsIds = queryable
.Where(x => x.ParentId.HasValue)
.Select(x => x.ParentId.Value)
.Distinct()
.ToArray();
queryable = queryable.Union(
dbContext.Manuals
.AsNoTracking()
.Where(x => parentsIds.Contains(x.Id))
);
}
var ids = queryable
.OrderBy(/* condition */)
.Select(x => x.Id)
.Skip(page * pageSize)
.Take(pageSize)
.ToList(); // Should execute union without worrying about column transformation since we have requested only IDs.
var manuals = await dbContext.Manuals.Where(x => ids.Contains(x.Id)).ToListAsync();
至少要考虑几个选项。
我在我的项目中使用 EF 6。我只想从数据库中获取一些数据,有时加入额外的数据。
这是我的代码:
var queryable = dbContext.Manuals
.AsNoTracking()
.Where(x => x.Status == "ACTIVE");
if (showDisabledParents)
{
var parentsIds = queryable
.Where(x => x.ParentId.HasValue)
.Select(x => x.ParentId.Value)
.Distinct()
.ToArray();
queryable = queryable.Union(
dbContext.Manuals
.AsNoTracking()
.Where(x => parentsIds.Contains(x.Id))
);
}
当 showDisabledParents 为 false 时效果很好。 EF 将生成具有正确列名的 SQL 查询。
但是当 showDisabledParents 为 true 时,EF 生成 UNION 语句(这是预期的)但它使用 C1、C2、C3 .. . 对于列名。
问题是我有一个自定义 DbDataReader,它调用 DateTime.SpecifyKind(..., DateTimeKind.Utc) 如果列名以 "Utc" 结尾。由于 EF 使用了错误的列名(C1、C2、C3 等),我的逻辑无法正常工作。
是否可以通过某种方式阻止这种行为?我的意思是,如果有办法告诉 EF 不要使用这些奇怪的列名。
更新: 这是我的 DBDataReaderCode:
public class MyDbDataReader : DelegatingDbDataReader
{
private string _dbName;
public MyDbDataReader(string dbName, DbDataReader source)
: base(source)
{
_dbName = dbName;
}
public override DateTime GetDateTime(int ordinal)
{
return DateTime.SpecifyKind(base.GetDateTime(ordinal), base.GetName(ordinal).EndsWith("UTC", StringComparison.OrdinalIgnoreCase)
? DateTimeKind.Utc
: DateTimeKind.Local);
}
}
我认为这是不可能的,因为 EF 使用内部别名来生成整个查询,这样可以确保从不同表等收集的名称中没有重复项。
无论如何,我宁愿搜索有关您处理日期的问题。您能否提供有关您的案例的更多详细信息?一般来说,经验法则是始终以 UTC 格式保留日期并根据需要转换为特定时区。
我用于处理 DateTime.Kind 的典型方法是在适用的实体 属性 上使用属性。例如,这将在任何 UTC:
的 DateTime 实体 属性 上进行 [DateTimeKind(DateTimeKind.Utc)]
public DateTime SomeDateUTC { get; set; }
如果您希望将其他日期时间标记为当地时间:
[DateTimeKind(DateTimeKind.Local)]
public DateTime SomeDate { get; set; }
没有属性的 DateTimes 将保留为未指定类型。
属性本身:参见 (Entity Framework DateTime and UTC)
然后在您的 DbContext 中,您只需将其添加到 InitializeContext 方法中:
((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
(sender, e) => DateTimeKindAttribute.Apply(e.Entity);
否则,如果你想坚持你现在的方法,而且你依赖于列名,Union
不能指望,所以将条件拆分为两个查询。这将需要在执行查询的任何地方完成,因此如果您的方法返回 IQueryable<Manual>
,则需要将其更改为 IEnumerable<IQueryable<Manual>>
,如果返回多个查询,则合并结果。
var queryables = new List<IQueryable<Manual>();
queryables.Add(dbContext.Manuals
.AsNoTracking()
.Where(x => x.Status == "ACTIVE"));
if (showDisabledParents)
{
var parentsIds = dbContext.Manuals
.Where(x => x.Status == "ACTIVE"
&& x.ParentId.HasValue)
.Select(x => x.ParentId.Value)
.Distinct()
.ToArray();
queryables.Add = dbContext.Manuals
.AsNoTracking()
.Where(x => parentsIds.Contains(x.Id));
}
return queryables;
如果您改为在此方法中执行查询,则只需捕获第二个查询(如果需要)并合并要返回的结果。
这里最棘手的一点是,如果您需要对组合集进行分页。在这种情况下,我会改为使用 two-pass 方法,在适当排序后获取相关任务 ID,然后对 ID 使用分页提取,按 ID 加载实体:
var queryable = dbContext.Manuals
.AsNoTracking()
.Where(x => x.Status == "ACTIVE");
if (showDisabledParents)
{
var parentsIds = queryable
.Where(x => x.ParentId.HasValue)
.Select(x => x.ParentId.Value)
.Distinct()
.ToArray();
queryable = queryable.Union(
dbContext.Manuals
.AsNoTracking()
.Where(x => parentsIds.Contains(x.Id))
);
}
var ids = queryable
.OrderBy(/* condition */)
.Select(x => x.Id)
.Skip(page * pageSize)
.Take(pageSize)
.ToList(); // Should execute union without worrying about column transformation since we have requested only IDs.
var manuals = await dbContext.Manuals.Where(x => ids.Contains(x.Id)).ToListAsync();
至少要考虑几个选项。