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将理解的数组的比较?或者将数组强制转换为其他类型,以便比较可以通过编译器?
找到一个完美的解决方法!在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使用left和right参数复制表达式,但不复制MethodInfo我们用于漏洞的参数.)
您可以使用SqlQuery编写原始SQL而不是生成它.
MyContext.Foos.SqlQuery("SELECT * FROM Foos WHERE Version > @ver", new SqlParameter("ver", lastFoo.Version));
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5960 次 |
| 最近记录: |