How to query Code First entities based on rowversion/timestamp value?

前端 未结 10 1137
忘了有多久
忘了有多久 2020-12-14 17:56

I\'ve run into a case where something that worked fairly well with LINQ to SQL seems to be very obtuse (or maybe impossible) with the Entity Framework. Specifically, I\'ve g

相关标签:
10条回答
  • 2020-12-14 18:20

    That is the best solution, but have a performance issue. The parameter @ver will be cast. Cast columns in where clause are bad to the database.

    Type conversion in expression may affect "SeekPlan" in query plan choice

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

    Without cast. MyContext.Foos.SqlQuery("SELECT * FROM Foos WHERE Version > @ver", new SqlParameter("ver", lastFoo.Version).SqlDbType = SqlDbType.Timestamp);

    0 讨论(0)
  • 2020-12-14 18:26

    (The following answer by Damon Warren is copied over from here):

    Here is what we did to solve this:

    Use a compare extension like this:

    public static class EntityFrameworkHelper
        {
            public static int Compare(this byte[] b1, byte[] b2)
            {
                throw new Exception("This method can only be used in EF LINQ Context");
            }
        }
    

    Then you can do

    byte[] rowversion = .....somevalue;
    _context.Set<T>().Where(item => item.RowVersion.Compare(rowversion) > 0);
    

    The reason this works without a C# implementation is because the compare extension method is never actually called, and EF LINQ simplifies x.compare(y) > 0 down to x > y

    0 讨论(0)
  • 2020-12-14 18:29

    You can use SqlQuery to write the raw SQL instead of having it generated.

    MyContext.Foos.SqlQuery("SELECT * FROM Foos WHERE Version > @ver", new SqlParameter("ver", lastFoo.Version));
    
    0 讨论(0)
  • 2020-12-14 18:29

    I ended up executing a raw query:
    ctx.Database.SqlQuery("SELECT * FROM [TABLENAME] WHERE(CONVERT(bigint,@@DBTS) >" + X)).ToList();

    0 讨论(0)
  • 2020-12-14 18:30

    Here is yet another workaround available to EF 6.x that doesn't require creating functions in the database but uses model defined functions instead.

    Function definitions (this goes inside the section in your CSDL file, or inside section if you are using EDMX files):

    <Function Name="IsLessThan" ReturnType="Edm.Boolean" >
      <Parameter Name="source" Type="Edm.Binary" MaxLength="8" />
      <Parameter Name="target" Type="Edm.Binary" MaxLength="8" />
      <DefiningExpression>source &lt; target</DefiningExpression>
    </Function>
    <Function Name="IsLessThanOrEqualTo" ReturnType="Edm.Boolean" >
      <Parameter Name="source" Type="Edm.Binary" MaxLength="8" />
      <Parameter Name="target" Type="Edm.Binary" MaxLength="8" />
      <DefiningExpression>source &lt;= target</DefiningExpression>
    </Function>
    <Function Name="IsGreaterThan" ReturnType="Edm.Boolean" >
      <Parameter Name="source" Type="Edm.Binary" MaxLength="8" />
      <Parameter Name="target" Type="Edm.Binary" MaxLength="8" />
      <DefiningExpression>source &gt; target</DefiningExpression>
    </Function>
    <Function Name="IsGreaterThanOrEqualTo" ReturnType="Edm.Boolean" >
      <Parameter Name="source" Type="Edm.Binary" MaxLength="8" />
      <Parameter Name="target" Type="Edm.Binary" MaxLength="8" />
      <DefiningExpression>source &gt;= target</DefiningExpression>
    </Function>
    

    Note that I haven't written the code to create the functions using the APIs available in Code First, but similar to code to what Drew proposed or the model conventions I wrote some time ago for UDFs https://github.com/divega/UdfCodeFirstSample, should work

    Method definition (this goes in your C# source code):

    using System.Collections;
    using System.Data.Objects.DataClasses;
    
    namespace TimestampComparers
    {
        public static class TimestampComparers
        {
    
            [EdmFunction("TimestampComparers", "IsLessThan")]
            public static bool IsLessThan(this byte[] source, byte[] target)
            {
                return StructuralComparisons.StructuralComparer.Compare(source, target) == -1;
            }
    
            [EdmFunction("TimestampComparers", "IsGreaterThan")]
            public static bool IsGreaterThan(this byte[] source, byte[] target)
            {
                return StructuralComparisons.StructuralComparer.Compare(source, target) == 1;
            }
    
            [EdmFunction("TimestampComparers", "IsLessThanOrEqualTo")]
            public static bool IsLessThanOrEqualTo(this byte[] source, byte[] target)
            {
                return StructuralComparisons.StructuralComparer.Compare(source, target) < 1;
            }
    
            [EdmFunction("TimestampComparers", "IsGreaterThanOrEqualTo")]
            public static bool IsGreaterThanOrEqualTo(this byte[] source, byte[] target)
            {
                return StructuralComparisons.StructuralComparer.Compare(source, target) > -1;
            }
        }
    }
    

    Note also that I have defined the methods as extension methods over byte[], although this is not necessary. I also provided implementations for the methods so that they work if you evaluate them outside queries, but you can choose as well to throw NotImplementedException. When you use these methods in LINQ to Entities queries, we will never really invoke them. Also not that I have made the first argument for EdmFunctionAttribute “TimestampComparers”. This has to match the namespace specified in the section of your conceptual model.

    Usage:

    using System.Linq;
    
    namespace TimestampComparers
    {
        class Program
        {
            static void Main(string[] args)
            {
                using (var context = new OrdersContext())
                {
                    var stamp = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, };
    
                    var lt = context.OrderLines.FirstOrDefault(l => l.TimeStamp.IsLessThan(stamp));
                    var lte = context.OrderLines.FirstOrDefault(l => l.TimeStamp.IsLessThanOrEqualTo(stamp));
                    var gt = context.OrderLines.FirstOrDefault(l => l.TimeStamp.IsGreaterThan(stamp));
                    var gte = context.OrderLines.FirstOrDefault(l => l.TimeStamp.IsGreaterThanOrEqualTo(stamp));
    
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-14 18:32

    I extended jnm2’s answer to hide the ugly expression code in a extension method

    Usage:

    ctx.Foos.WhereVersionGreaterThan(r => r.RowVersion, myVersion);
    

    Extension Method:

    public static class RowVersionEfExtensions
    {
    
    
        private static readonly MethodInfo BinaryGreaterThanMethodInfo = typeof(RowVersionEfExtensions).GetMethod(nameof(BinaryGreaterThanMethod), BindingFlags.Static | BindingFlags.NonPublic);
        private static bool BinaryGreaterThanMethod(byte[] left, byte[] right)
        {
            throw new NotImplementedException();
        }
    
        private static readonly MethodInfo BinaryLessThanMethodInfo = typeof(RowVersionEfExtensions).GetMethod(nameof(BinaryLessThanMethod), BindingFlags.Static | BindingFlags.NonPublic);
        private static bool BinaryLessThanMethod(byte[] left, byte[] right)
        {
            throw new NotImplementedException();
        }
    
        /// <summary>
        /// Filter the query to return only rows where the RowVersion is greater than the version specified
        /// </summary>
        /// <param name="query">The query to filter</param>
        /// <param name="propertySelector">Specifies the property of the row that contains the RowVersion</param>
        /// <param name="version">The row version to compare against</param>
        /// <returns>Rows where the RowVersion is greater than the version specified</returns>
        public static IQueryable<T> WhereVersionGreaterThan<T>(this IQueryable<T> query, Expression<Func<T, byte[]>> propertySelector, byte[] version)
        {
            var memberExpression = propertySelector.Body as MemberExpression;
            if (memberExpression == null) { throw new ArgumentException("Expression should be of form r=>r.RowVersion"); }
            var propName = memberExpression.Member.Name;
    
            var fooParam = Expression.Parameter(typeof(T));
            var recent = query.Where(Expression.Lambda<Func<T, bool>>(
                Expression.GreaterThan(
                    Expression.Property(fooParam, propName),
                    Expression.Constant(version),
                    false,
                    BinaryGreaterThanMethodInfo),
                fooParam));
            return recent;
        }
    
    
        /// <summary>
        /// Filter the query to return only rows where the RowVersion is less than the version specified
        /// </summary>
        /// <param name="query">The query to filter</param>
        /// <param name="propertySelector">Specifies the property of the row that contains the RowVersion</param>
        /// <param name="version">The row version to compare against</param>
        /// <returns>Rows where the RowVersion is less than the version specified</returns>
        public static IQueryable<T> WhereVersionLessThan<T>(this IQueryable<T> query, Expression<Func<T, byte[]>> propertySelector, byte[] version)
        {
            var memberExpression = propertySelector.Body as MemberExpression;
            if (memberExpression == null) { throw new ArgumentException("Expression should be of form r=>r.RowVersion"); }
            var propName = memberExpression.Member.Name;
    
            var fooParam = Expression.Parameter(typeof(T));
            var recent = query.Where(Expression.Lambda<Func<T, bool>>(
                Expression.LessThan(
                    Expression.Property(fooParam, propName),
                    Expression.Constant(version),
                    false,
                    BinaryLessThanMethodInfo),
                fooParam));
            return recent;
        }
    
    
    
    }
    
    0 讨论(0)
提交回复
热议问题