c# - 如何根据 rowversion/timestamp 值查询 Code First 实体?

标签 c# sql-server entity-framework ef-code-first rowversion

我遇到过这样的情况,在 LINQ to SQL 中工作得很好的东西在 Entity Framework 中似乎非常迟钝(或者可能是不可能的)。具体来说,我有一个包含 rowversion 属性的实体(用于版本控制和并发控制)。像这样的东西:

public class Foo
{
  [Key]
  [MaxLength(50)]
  public string FooId { get; set; }

  [Timestamp]
  [ConcurrencyCheck]
  public byte[] Version { get; set; }
}

我希望能够将一个实体作为输入,并找到最近更新的所有其他实体。像这样的东西:

Foo lastFoo = GetSomeFoo();
var recent = MyContext.Foos.Where(f => f.Version > lastFoo.Version);

现在,在数据库中这会起作用:两个 rowversion 值可以毫无问题地相互比较。而我在使用LINQ to SQL之前也做过类似的事情,将rowversion映射到System.Data.Linq.Binary,可以进行比较。 (至少在表达式树可以映射回数据库的范围内。)

但是在Code First中,属性的类型必须是byte[]。并且两个数组不能用常规比较运算符进行比较。有没有其他方法可以编写 LINQ to Entities 可以理解的数组比较?或者将数组强制转换为其他类型,以便比较可以通过编译器?

最佳答案

找到了一个完美的解决方法!在 Entity Framework 6.1.3 上测试。

无法使用 <运算符与字节数组,因为 C# 类型系统阻止了这种情况(它应该这样做)。但是您可以做的是使用表达式构建完全相同的语法,并且有一个漏洞可以让您实现这一目标。

第一步

如果您不想要完整的解释,可以跳到解决方案部分。

如果您不熟悉表达式,这里是 MSDN's crash course .

基本上,当您输入 queryable.Where(obj => obj.Id == 1)编译器实际上输出的内容与您输入的内容相同:

var objParam = Expression.Parameter(typeof(ObjType));
queryable.Where(Expression.Lambda<Func<ObjType, bool>>(
    Expression.Equal(
        Expression.Property(objParam, "Id"),
        Expression.Constant(1)),
    objParam))

并且该表达式是数据库提供程序解析以创建您的查询的内容。这显然比原来的要冗长得多,但它也允许您像进行反射一样进行元编程。冗长是这种方法的唯一缺点。与此处的其他答案相比,这是一个更好的缺点,例如必须编写原始 SQL 或无法使用参数。

在我的例子中,我已经在使用表达式,但在你的例子中,第一步是使用表达式重写你的查询:

Foo lastFoo = GetSomeFoo();
var fooParam = Expression.Parameter(typeof(Foo));
var recent = MyContext.Foos.Where(Expression.Lambda<Func<Foo, bool>>(
    Expression.LessThan(
        Expression.Property(fooParam, nameof(Foo.Version)),
        Expression.Constant(lastFoo.Version)),
    fooParam));

这就是我们如何避免在尝试使用 < 时遇到的编译器错误在 byte[]对象。现在不是编译器错误,而是运行时异常,因为 Expression.LessThan试图找到 byte[].op_LessThan并在运行时失败。 这就是漏洞所在。

漏洞

为了消除运行时错误,我们将告诉 Expression.LessThan使用什么方法,这样它就不会尝试找到不存在的默认方法 ( byte[].op_LessThan):

var recent = MyContext.Foos.Where(Expression.Lambda<Func<Foo, bool>>(
    Expression.LessThan(
        Expression.Property(fooParam, nameof(Foo.Version)),
        Expression.Constant(lastFoo.Version),
        false,
        someMethodThatWeWrote), // So that Expression.LessThan doesn't try to find the non-existent default operator method
    fooParam));

太棒了!现在我们只需要 MethodInfo someMethodThatWeWrote从带有签名的静态方法创建 bool (byte[], byte[])以便类型在运行时与我们的其他表达式匹配。

解决方案

你需要一个小DbFunctionExpressions.cs .这是一个截断的版本:

public static class DbFunctionExpressions
{
    private static readonly MethodInfo BinaryDummyMethodInfo = typeof(DbFunctionExpressions).GetMethod(nameof(BinaryDummyMethod), BindingFlags.Static | BindingFlags.NonPublic);
    private static bool BinaryDummyMethod(byte[] left, byte[] right)
    {
        throw new NotImplementedException();
    }

    public static Expression BinaryLessThan(Expression left, Expression right)
    {
        return Expression.LessThan(left, right, false, BinaryDummyMethodInfo);
    }
}

用法

var recent = MyContext.Foos.Where(Expression.Lambda<Func<Foo, bool>>(
    DbFunctionExpressions.BinaryLessThan(
        Expression.Property(fooParam, nameof(Foo.Version)),
        Expression.Constant(lastFoo.Version)),            
    fooParam));
  • 享受吧。

注意事项

不适用于 Entity Framework Core 1.0.0,但我 opened an issue那里有更全面的支持,无论如何都不需要表达。 (EF Core 不起作用,因为它经历了一个阶段,它使用 LessThanleft 参数复制 right 表达式,但不复制我们用于漏洞的 MethodInfo 参数。)

关于c# - 如何根据 rowversion/timestamp 值查询 Code First 实体?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7437970/

相关文章:

c# - 在子查询中涉及计数的高效查询

c# - 如何使用 Unity 将基于 2D 数组的图 block 实例化到平台游戏中?

sql - MS SQL 选择表

c# - 表达式——如何重用业务逻辑?如何将它们结合起来?

.net - Entity Framework 应用程序中的虚假重复键错误

LINQ 查询相关属性中的 ofType

c# - 使用 XmlWriter 附加到 XML 文件

c# - 椰枣培养物

sql - 将数据类型 nvarchar 转换为数字时出错 - SQL Server

mysql - db.Database.SqlQuery 原始数据返回存储过程名称和参数