【问题标题】:Reading either byte[] or float with Linq to Entities使用 Linq to Entities 读取 byte[] 或 float
【发布时间】:2018-06-26 08:02:02
【问题描述】:

我有一个表,其中一列是二进制的,另一列是浮动的。我需要使用 SQL 选择以下内容

SELECT ISNULL(myBinary, myFloat) FROM table

这行得通,我得到一列包含所有二进制文件

0x3F800000
0xE5C13DBAB611123B47A7
0x9946C3BA9946C3BA9946
0xDE0E1D3C8B7A143C6DB7
0x3F800000

等等

现在我想使用 Linq to Entities 进行此查询,但是我找不到可以编译的代码

context.table.select(s => new MyObject()
  { 
     Result = s.myBinary ?? s.myFloat // <--- '??' operator cannot be applied to operands of type 'byte[]' and 'float'
  });

 class MyObject { public Object Result {get; set;} }

我如何获得这些值? BitConverter 也不起作用(在浮动上)

更新

我为什么要问:所以如果我要分别选择两列,我会得到更多的执行时间

set statistics time on
SELECT TOP (5000) ISNULL([x], [y])
  FROM [table];
set statistics time off
set statistics time on
SELECT TOP (5000) [x], [y]
  FROM [table];
  set statistics time off

yields(即使多次执行,也总是差不多)

(5000 rows affected)

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 7 ms.

(5000 rows affected)

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 113 ms.

注意:在一个评论中,我写了因子 40,这是使用另一种测量方法。统计时间大约是 10 倍。

如果我增加行数,我会得到

(50000 rows affected)

 SQL Server Execution Times:
   CPU time = 47 ms,  elapsed time = 820 ms. (with ISNULL)

(50000 rows affected)

 SQL Server Execution Times:
   CPU time = 16 ms,  elapsed time = 1365 ms.

如果行数较少(大约 1000 或更少),执行时间无法平均测量,因此大约为 1 毫秒。但是:我预计每个查询大约有 5000 到 10000 行。

【问题讨论】:

  • 试试投射吧?例如Result = (object)s.myBinary ?? (object)s.myFloat。我同意 DavidG - 你可能不应该这样做。
  • 您希望您的应用如何处理floatbyte[]?你留下了可怕的东西,比如反射。为什么不只返回两个值并使用二进制列,除非它为空?
  • 嗯...我得到 无法将类型“System.Byte[]”转换为类型“System.Object”。 LINQ to Entities 仅支持转换 EDM 基元或枚举类型。
  • @DavidG 性能有问题。阅读两者都会产生过多的流量。始终读取浮点值且始终不为空。但我只需要在二进制条目为空的情况下。
  • 可能的解决方案:创建一个执行 SELECT ISNULL(myBinary, myFloat) FROM table 的视图并将其映射到 EF

标签: c# entity-framework linq asp.net-web-api


【解决方案1】:

我可以提供的解决方案是自定义存储函数,映射到内置的ISNULL SQL 函数。

如果您使用 Code First 模型,则需要 EntityFramework.Functions 包。

但由于您使用的是 edmx,因此该过程有点复杂,需要手动编辑 edmx 文件。 How to: Define Custom Functions in the Storage Model MSDN 主题部分涵盖了该技术。

使用 XML(文本)编辑器打开您的 edmx 文件。找到edmx:StorageModels 元素的Schema 子元素并在里面添加以下内容:

<Function Name="IsNull" BuiltIn="true" IsComposable="true" ReturnType="binary">
  <Parameter Name="expr1" Type="binary" />
  <Parameter Name="expr2" Type="float" />
</Function>

请注意,如 MSDN 链接中所述:

如果您使用更新模型向导更新您的模型,将覆盖对 .edmx 文件的 SSDL 部分所做的更改(如以下过程中所建议的)。

因此,请确保将其保存在某个安全的地方,如果您从数据库更新 edmx,请重新包含它。

然后在某个静态类中添加一个方法,并用DbFunction 属性装饰它:

public static class CustomDbFunctions
{
    const string Namespace = "EFTest.MyDbContextModel.Store";

    [DbFunction(Namespace, "IsNull")]
    public static byte[] IsNull(byte[] expr1, double expr2) => throw new NotSupportedException();
}

(更新Namespace 字符串以匹配&lt;edmx:StorageModels&gt;&lt;Schema&gt; 元素的Namespace 属性值)。

仅此而已。现在您应该可以在 LINQ to Entities 查询中使用上述函数了:

class MyObject { public byte[] Result { get; set;} }

context.MyTable.Select(e => new MyObject
{ 
    Result = CustomDbFunctions.IsNull(e.myBinary, e.myFloat)
});

EF6 很乐意将其转换为所需的 SQL ISNULL 函数。

【讨论】:

  • 我测试了它,它似乎工作得很好......给我几个小时,我会用真实的生产数据仔细检查它
【解决方案2】:

你试图让它工作的方式的问题是你需要诉诸一些讨厌的黑客来确定你的返回值是byte[]还是float。这就是为什么您一直试图让 EF 查询返回 object。可以通过将float 破解为byte[] 来使其工作,但我会建议一种更简单且更合乎逻辑的方法:返回两个值并让应用程序决定要做什么。例如,返回类似这样的对象:

public class FloatOrByte // Do not call it this!
{
    public byte[] MyBinary { get; set; }
    public float MyFloat { get; set; }
}

然后像这样返回它:

var result = context.table.Select(s => new FloatOrByte
{ 
    MyBinary = s.myBinary,
    MyFloat = s.myFloat
};

现在您可以检查 null 的值:

if(result.MyBinary != null)
{
    // Do stuff with the byte value
}
else
{
    // Do stuff with the float value
}

【讨论】:

  • 是的,我现在已经实现了,但这会使代码太慢。在 op 中执行 SQL 显然比返回两列要快。
  • 真的存在性能问题吗?当然,它并不完美,但我怀疑返回一个并不总是需要的单个浮点值会影响您的应用,尤其是当您返回一个可能很大的 byte[] 时。
  • 好吧,我真的不能说,因为我还没有让代码工作。但是,只有在 SQL Server Manager 中工作,我才能获得性能。使用 OP SQL 提升。是的,现在对客户来说太慢了。
  • 您如何判断使用 SQL Server 管理器会提高性能?这对我来说没有意义。
  • 限定执行一个 SQL 查询的时间与执行另一个 SQL 查询的时间。
【解决方案3】:

请更改您的类定义以接受空值。

public class FloatOrByte 
{
    public byte[] MyBinary { get; set; }
    public float? MyFloat { get; set; }
}

然后使用您的 DBContext 类获取值。问题是 EF 无法将 null-coalescing 运算符转换为 SQL。

var result = context.table.Select(s => s);

var processResult = result.Tolist().Select(s=> new MyObject{

    Result = (s.MyBinary == null) ? (object)s.MyFloat : (object)s.MyBinary 

 });

【讨论】:

  • 引用:操作员'??'不能应用于“byte[]”和“float”类型的操作数
  • 请阅读 OP 的 cmets:“无法将类型 'System.Byte[]' 转换为类型 'System.Object'”
  • 任何类型都可以转换为对象类型,甚至是用户定义的类型。我希望您的 MyObject 类具有“结果”属性作为对象类型,它在我的代码中运行良好。请尝试
  • 错误消息:无法将类型“System.Single”转换为类型“System.Object”。 LINQ to Entities 仅支持转换 EDM 基元或枚举类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多