Comment interroger les premières entités du Code en fonction de la valeur rowversion/timestamp?

j'ai rencontré un cas où quelque chose qui fonctionnait assez bien avec LINQ à SQL semble être très obtus (ou peut-être impossible) avec le Framework Entity. Plus précisément, j'ai une entité qui comprend un rowversion propriété (à la fois pour versioning et contrôle de simultanéité). Quelque chose comme:

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

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

je voudrais pouvoir prendre une entity comme entrée, et trouver toutes les autres entities qui sont plus récemment mises à jour. Quelque chose comme:

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

Maintenant, dans le base de données cela fonctionnerait: deux rowversion les valeurs peuvent être comparées les unes aux autres sans aucun problème. Et J'ai fait la même chose avant D'utiliser LINQ pour SQL, qui correspond à la rowversionSystem.Data.Linq.Binary, qui peut être comparé. (Au moins dans la mesure où l'arbre des expressions peut être mappé vers la base de données.)

mais en Code D'abord, le type de la propriété doit être byte[]. Et deux tableaux ne peuvent pas être comparés avec les opérateurs de comparaison. Est-il une autre façon d'écrire l' comparaison des tableaux que LINQ à des entités comprendra? Ou pour forcer les tableaux dans d'autres types de sorte que la comparaison peut passer le compilateur?

19
demandé sur Sixten Otto 2011-09-16 02:06:29

9 réponses

Vous pouvez utiliser SqlQuery pour écrire le SQL brut au lieu de le faire générer.

MyContext.Foos.SqlQuery("SELECT * FROM Foos WHERE Version > @ver", new SqlParameter("ver", lastFoo.Version));
5
répondu Josh 2011-09-16 18:27:39

Trouvé une solution qui fonctionne parfaitement! Testé sur le cadre de L'entité 6.1.3.

Il n'y a pas moyen d'utiliser le < opérateur avec tableaux d'octets parce que le système de type C empêche cela (comme il se doit). Mais ce que vous do est de construire la même syntaxe exacte en utilisant des expressions, et il y a une faille qui vous permet de l'enlever.

Première étape

Si vous ne voulez pas l'explication complète, vous pouvez passer à la Solution section.

si vous n'êtes pas familier avec les expressions, voici cours accéléré de MSDN.

en gros, quand vous tapez queryable.Where(obj => obj.Id == 1) le compilateur affiche vraiment la même chose que si vous aviez tapé:

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

et cette expression est ce que le fournisseur de base de données analyse pour créer votre requête. C'est évidemment beaucoup plus verbeux que l'original, mais il vous permet aussi de faire de la méta-programmation comme quand vous faites de la réflexion. Le niveau de verbosité est l' seul inconvénient à cette méthode. C'est un meilleur inconvénient que les autres réponses ici, comme devoir écrire du SQL brut ou ne pas pouvoir utiliser les paramètres.

dans mon cas, j'utilisais déjà des expressions, mais dans votre cas la première étape est de réécrire votre requête en utilisant des expressions:

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));

C'est la façon dont nous contourner l'erreur du compilateur, nous obtenons si nous essayons d'utiliser <byte[] objets. Maintenant, au lieu d'une erreur du compilateur, nous obtenons une exception d'exécution parce que Expression.LessThan essaie de trouver byte[].op_LessThan et échoue à l'exécution. C'est là que la faille vient dans.

Echappatoire

Pour se débarrasser de l'erreur d'exécution, nous dirons Expression.LessThan quelle méthode utiliser pour ne pas essayer de trouver la méthode par défaut (byte[].op_LessThan) qui n'existe pas:

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));

Super! Maintenant tout ce dont nous avons besoin est MethodInfo someMethodThatWeWrote créé à partir d'une méthode statique avec la signature bool (byte[], byte[]) de sorte que les types correspondent à l'exécution avec nos autres expression.

Solution

Vous avez besoin d'un petit DbFunctionExpressions.cs. Voici une version tronquée:

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);
    }
}

Utilisation

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

Notes

ne fonctionne pas sur le noyau du cadre de L'entité 1.0.0, mais je ouvert un problème là pour un support plus complet sans avoir de toute façon besoin d'expressions. (EF Core ne fonctionne pas parce qu'il passe par une étape où il des copies de la LessThan expression left et right paramètres mais ne copie pas les MethodInfo paramètre que nous utilisons pour la lacune.)

9
répondu jnm2 2016-07-01 18:38:01

vous pouvez accomplir ceci dans le code EF 6-d'abord en faisant correspondre une fonction C# à une fonction de base de données. Il a fallu quelques ajustements et ne produit pas le SQL le plus efficace, mais il obtient le travail fait.

tout d'Abord, créer une fonction dans la base de données pour tester une nouvelle rowversion. Le mien est

CREATE FUNCTION [common].[IsNewerThan]
(
    @CurrVersion varbinary(8),
    @BaseVersion varbinary(8)
) ...

lors de la construction de votre contexte EF, vous devrez définir manuellement la fonction dans le modèle store, comme ceci:

private static DbCompiledModel GetModel()
{
    var builder = new DbModelBuilder();
    ... // your context configuration
    var model = builder.Build(...); 
    EdmModel store = model.GetStoreModel();
    store.AddItem(GetRowVersionFunctionDef(model));
    DbCompiledModel compiled = model.Compile();
    return compiled;
}

private static EdmFunction GetRowVersionFunctionDef(DbModel model)
{
    EdmFunctionPayload payload = new EdmFunctionPayload();
    payload.IsComposable = true;
    payload.Schema = "common";
    payload.StoreFunctionName = "IsNewerThan";
    payload.ReturnParameters = new FunctionParameter[]
    {
        FunctionParameter.Create("ReturnValue", 
            GetStorePrimitiveType(model, PrimitiveTypeKind.Boolean), ParameterMode.ReturnValue)
    };
    payload.Parameters = new FunctionParameter[]
    {
        FunctionParameter.Create("CurrVersion",  GetRowVersionType(model), ParameterMode.In),
        FunctionParameter.Create("BaseVersion",  GetRowVersionType(model), ParameterMode.In)
    };
    EdmFunction function = EdmFunction.Create("IsRowVersionNewer", "EFModel",
        DataSpace.SSpace, payload, null);
    return function;
}

private static EdmType GetStorePrimitiveType(DbModel model, PrimitiveTypeKind typeKind)
{
    return model.ProviderManifest.GetStoreType(TypeUsage.CreateDefaultTypeUsage(
        PrimitiveType.GetEdmPrimitiveType(typeKind))).EdmType;
}

private static EdmType GetRowVersionType(DbModel model)
{
    // get 8-byte array type
    var byteType = PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Binary);
    var usage = TypeUsage.CreateBinaryTypeUsage(byteType, true, 8);

    // get the db store type
    return model.ProviderManifest.GetStoreType(usage).EdmType;
}

Créer un proxy pour la méthode par décorer une méthode statique avec l'attribut DbFunction. EF utilise pour associer la méthode avec la méthode nommée dans le modèle de magasin. Le faire une méthode d'extension produit LINQ plus propre.

[DbFunction("EFModel", "IsRowVersionNewer")]
public static bool IsNewerThan(this byte[] baseVersion, byte[] compareVersion)
{
    throw new NotImplementedException("You can only call this method as part of a LINQ expression");
}

Exemple

enfin, appelez la méthode de LINQ à entities dans une expression standard.

    using (var db = new OrganizationContext(session))
    {
        byte[] maxRowVersion = db.Users.Max(u => u.RowVersion);
        var newer = db.Users.Where(u => u.RowVersion.IsNewerThan(maxRowVersion)).ToList();
    }

génère le T-SQL pour obtenir ce que vous voulez, en utilisant le contexte et les ensembles d'entités que vous avez définis.

WHERE ([common].[IsNewerThan]([Extent1].[RowVersion], @p__linq__0)) = 1',N'@p__linq__0 varbinary(8000)',@p__linq__0=0x000000000001DB7B
3
répondu drew 2013-11-26 19:13:57

cette méthode fonctionne pour moi et évite d'altérer le SQL brut:

var recent = MyContext.Foos.Where(c => BitConverter.ToUInt64(c.RowVersion.Reverse().ToArray(), 0) > fromRowVersion);

je suppose cependant SQL brut serait plus efficace.

1
répondu Adsborough 2014-04-03 06:57:03

j'ai trouvé cette solution de contournement utile:

byte[] rowversion = BitConverter.GetBytes(revision);

var dbset = (DbSet<TEntity>)context.Set<TEntity>();

string query = dbset.Where(x => x.Revision != rowversion).ToString()
    .Replace("[Revision] <> @p__linq__0", "[Revision] > @rowversion");

return dbset.SqlQuery(query, new SqlParameter("rowversion", rowversion)).ToArray();
0
répondu Jekas 2012-09-09 12:12:54

j'ai fini par exécuter une requête brute:

ctx.La base de données.SqlQuery ("SELECT * FROM [TABLENAME] WHERE(CONVERT (bigint,@DBTS) >" + X)).ToList ();

0
répondu David Lorenzin 2014-11-12 12:11:27

C'est la meilleure solution, mais ont un problème de performance. Le paramètre @ver sera lancé. Les colonnes de fonte dans lesquelles la clause est mauvaise pour la base de données.

conversion de Type dans l'expression susceptible d'affecter "SeekPlan" dans le plan de requête choix

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

sans plâtre. MyContext.Foos.SqlQuery ("SELECT * FROM Foos WHERE Version > @ver" , Nouveau SqlParameter ("ver", lastFoo.Version.)SqlDbType = SqlDbType.Timestamp);

0
répondu André Mendonça 2015-03-18 15:59:02

Voici encore une autre solution disponible pour L'EF 6.x qui ne nécessite pas la création de fonctions dans la base de données mais utilise des fonctions définies par le modèle à la place.

définition des fonctions (ceci va à l'intérieur de la section dans votre fichier CSDL, ou à l'intérieur de la section si vous utilisez des fichiers EDMX):

<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>

notez que je n'ai pas écrit le code pour créer les fonctions en utilisant les API disponibles dans le Code en premier, mais similaire au code à ce que Drew a proposé ou les conventions modèles I écrit il y a quelques temps pour Udf https://github.com/divega/UdfCodeFirstSample, devrait fonctionner

définition de la Méthode (ce qui se passe dans votre code source en C#):

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;
        }
    }
}

Notez aussi que j'ai défini les méthodes comme des méthodes d'extension sur byte[], bien que cela ne soit pas nécessaire. J'ai également fourni des implémentations pour les méthodes de sorte qu'elles fonctionnent si vous les évaluez à l'extérieur des requêtes, mais vous pouvez aussi choisir de lancer NotImplementedException. Lorsque vous utilisez ces méthodes dans LINQ aux requêtes Entities, nous ne les invoquerons jamais vraiment. Aussi pas que j'ai fait le premier argument pour EdmFunctionAttribute "TimestampComparers". Cela doit correspondre à l'espace de noms spécifié dans la section de votre modèle conceptuel.

Utilisation:

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
répondu divega 2016-07-21 21:56:06

j'ai étendu jmn2s réponse pour cacher le code d'expression moche dans une méthode d'extension

Utilisation:

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

Méthode D'Extension:

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
répondu innominate227 2017-06-22 14:00:14