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

Six*_*tto 19 c# sql-server entity-framework rowversion ef-code-first

我遇到过一个案例,其中与LINQ to SQL相当不错的东西似乎对Entity Framework来说非常迟钝(或者可能是不可能的).具体来说,我有一个包含rowversion属性的实体(用于版本控制和并发控制).就像是:

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

  [Timestamp]
  [ConcurrencyCheck]
  public byte[] Version { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我希望能够将实体作为输入,并找到最近更新的所有其他实体.就像是:

Foo lastFoo = GetSomeFoo();
var recent = MyContext.Foos.Where(f => f.Version > lastFoo.Version);
Run Code Online (Sandbox Code Playgroud)

现在,在数据库中,这将起作用:两个rowversion值可以相互比较而没有任何问题.在使用LINQ to SQL之前我做了类似的事情,它映射了rowversionto System.Data.Linq.Binary,可以进行比较.(至少在表达式树可以映射回数据库的程度上.)

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

jnm*_*nm2 9

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

没有办法将<运算符与字节数组一起使用,因为C#类型系统会阻止它(应该如此).但你可以做的是使用表达式构建完全相同的语法,并且有一个漏洞可以让你解决这个问题.

第一步

如果您不想要完整的说明,可以跳到"解决方案"部分.

如果你不熟悉表达式,这里是MSDN的速成课程.

基本上,当您键入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))
Run Code Online (Sandbox Code Playgroud)

该表达式是数据库提供程序解析以创建查询的表达式.这显然比原始版本更冗长,但它也允许您像进行反射一样进行元编程.详细程度是这种方法的唯一缺点.这是一个比其他答案更好的缺点,比如必须编写原始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));
Run Code Online (Sandbox Code Playgroud)

这是我们如何绕过编译错误,我们得到,如果我们试图用<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));
Run Code Online (Sandbox Code Playgroud)

大!现在我们需要的是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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法

var recent = MyContext.Foos.Where(Expression.Lambda<Func<Foo, bool>>(
    DbFunctionExpressions.BinaryLessThan(
        Expression.Property(fooParam, nameof(Foo.Version)),
        Expression.Constant(lastFoo.Version)),            
    fooParam));
Run Code Online (Sandbox Code Playgroud)
  • 请享用.

笔记

不适用于Entity Framework Core 1.0.0,但是我那里提出了一个问题,无需表达式即可获得更全面的支持.(EF Core不起作用,因为它经历了一个阶段,它LessThan使用leftright参数复制表达式,但不复制MethodInfo我们用于漏洞的参数.)


Jos*_*osh 5

您可以使用SqlQuery编写原始SQL而不是生成它.

MyContext.Foos.SqlQuery("SELECT * FROM Foos WHERE Version > @ver", new SqlParameter("ver", lastFoo.Version));
Run Code Online (Sandbox Code Playgroud)