it-swarm.com.de

Problemumgehung für 'Contains ()' mit Linq to Entities?

Ich versuche, eine Abfrage zu erstellen, die eine Liste von IDs in der where-Klausel verwendet, wobei ich die API des Silverlight ADO.Net Data Services-Clients (und daher Linq To Entities) verwende. Kennt jemand eine Problemumgehung für Contains, die nicht unterstützt werden?

Ich möchte so etwas machen:

List<long?> txnIds = new List<long?>();
// Fill list 

var q = from t in svc.OpenTransaction
        where txnIds.Contains(t.OpenTransactionId)
        select t;

Versuchte dies:

var q = from t in svc.OpenTransaction
where txnIds.Any<long>(tt => tt == t.OpenTransactionId)
select t;

Aber bekam "Die Methode 'Any' wird nicht unterstützt".

85
James Bloomer

Update: EF ≥ 4 unterstützt Contains direkt (Checkout Any ), Sie brauchen also keine Problemumgehung.

public static IQueryable<TEntity> WhereIn<TEntity, TValue>
  (
    this ObjectQuery<TEntity> query,
    Expression<Func<TEntity, TValue>> selector,
    IEnumerable<TValue> collection
  )
{
  if (selector == null) throw new ArgumentNullException("selector");
  if (collection == null) throw new ArgumentNullException("collection");
  if (!collection.Any()) 
    return query.Where(t => false);

  ParameterExpression p = selector.Parameters.Single();

  IEnumerable<Expression> equals = collection.Select(value =>
     (Expression)Expression.Equal(selector.Body,
          Expression.Constant(value, typeof(TValue))));

  Expression body = equals.Aggregate((accumulate, equal) =>
      Expression.Or(accumulate, equal));

  return query.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
}

//Optional - to allow static collection:
public static IQueryable<TEntity> WhereIn<TEntity, TValue>
  (
    this ObjectQuery<TEntity> query,
    Expression<Func<TEntity, TValue>> selector,
    params TValue[] collection
  )
{
  return WhereIn(query, selector, (IEnumerable<TValue>)collection);
}

VERWENDUNG:

public static void Main()
{
  using (MyObjectContext context = new MyObjectContext())
  {
    //Using method 1 - collection provided as collection
    var contacts1 =
      context.Contacts.WhereIn(c => c.Name, GetContactNames());

    //Using method 2 - collection provided statically
    var contacts2 = context.Contacts.WhereIn(c => c.Name,
      "Contact1",
      "Contact2",
      "Contact3",
      "Contact4"
      );
  }
}
96
Shimmy

Sie können auf e-sql zurückgreifen, indem Sie das Schlüsselwort "it" eingeben:

return CurrentDataSource.Product.Where("it.ID IN {4,5,6}"); 

Hier ist der Code, mit dem ich E-SQL aus einer Sammlung, YMMV, generiert habe:

string[] ids = orders.Select(x=>x.ProductID.ToString()).ToArray();
return CurrentDataSource.Products.Where("it.ID IN {" + string.Join(",", ids) + "}");
18

Von MSDN :

static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
    Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
{
    if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
    if (null == values) { throw new ArgumentNullException("values"); }
    ParameterExpression p = valueSelector.Parameters.Single();

    // p => valueSelector(p) == values[0] || valueSelector(p) == ...
    if (!values.Any())
    {
        return e => false;
    }

    var equals = values.Select(
             value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));

    var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));

    return Expression.Lambda<Func<TElement, bool>>(body, p);
} 

und die Abfrage wird:

var query2 = context.Entities.Where(BuildContainsExpression<Entity, int>(e => e.ID, ids));
13
James Bloomer

Ich bin mir bei Silverligth nicht sicher, aber in linq to objects verwende ich immer any () für diese Abfragen.

var q = from t in svc.OpenTranaction
        where txnIds.Any(t.OpenTransactionId)
        select t;
2
AndreasN

Um die Aufzeichnung zu vervollständigen, hier ist der Code, den ich schließlich verwendet habe (die Fehlerprüfung wurde aus Gründen der Klarheit weggelassen) ...

// How the function is called
var q = (from t in svc.OpenTransaction.Expand("Currency,LineItem")
         select t)
         .Where(BuildContainsExpression<OpenTransaction, long>(tt => tt.OpenTransactionId, txnIds));



 // The function to build the contains expression
   static System.Linq.Expressions.Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
                System.Linq.Expressions.Expression<Func<TElement, TValue>> valueSelector, 
                IEnumerable<TValue> values)
        {
            if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
            if (null == values) { throw new ArgumentNullException("values"); }
            System.Linq.Expressions.ParameterExpression p = valueSelector.Parameters.Single();

            // p => valueSelector(p) == values[0] || valueSelector(p) == ...
            if (!values.Any())
            {
                return e => false;
            }

            var equals = values.Select(value => (System.Linq.Expressions.Expression)System.Linq.Expressions.Expression.Equal(valueSelector.Body, System.Linq.Expressions.Expression.Constant(value, typeof(TValue))));
            var body = equals.Aggregate<System.Linq.Expressions.Expression>((accumulate, equal) => System.Linq.Expressions.Expression.Or(accumulate, equal));
            return System.Linq.Expressions.Expression.Lambda<Func<TElement, bool>>(body, p);
        }
1
James Bloomer

Vielen Dank. Wobei Verlängerungsmethode für mich ausreichte. Ich habe es profiliert und den gleichen SQL-Befehl für die Datenbank generiert wie e-sql.

public Estado[] GetSomeOtherMore(int[] values)
{
    var result = _context.Estados.WhereIn(args => args.Id, values) ;
    return result.ToArray();
}

Erzeugt dies:

SELECT 
[Extent1].[intIdFRLEstado] AS [intIdFRLEstado], 
[Extent1].[varDescripcion] AS [varDescripcion]
FROM [dbo].[PVN_FRLEstados] AS [Extent1]
WHERE (2 = [Extent1].[intIdFRLEstado]) OR (4 = [Extent1].[intIdFRLEstado]) OR (8 = [Extent1].[intIdFRLEstado])
0
jrojo

Neben der ausgewählten Antwort.

Ersetzen Sie Expression.Or Durch Expression.OrElse, Um Nhibernate zu verwenden, und beheben Sie die Unable to cast object of type 'NHibernate.Hql.Ast.HqlBitwiseOr' to type 'NHibernate.Hql.Ast.HqlBooleanExpression' - Ausnahme.

0
smg

Tut mir leid, neuer Benutzer, ich hätte die eigentliche Antwort kommentiert, aber anscheinend kann ich das noch nicht?

In Bezug auf die Antwort mit Beispielcode für BuildContainsExpression () sollten Sie jedoch beachten, dass bei Verwendung dieser Methode für Datenbankentitäten (dh nicht speicherinterne Objekte) und bei Verwendung von IQueryable diese Methode tatsächlich in die Datenbank verschoben werden muss da es im Grunde eine Menge "oder" SQL-Bedingungen gibt, um die "where in" -Klausel zu überprüfen (führen Sie es mit SQL Profiler aus, um zu sehen).

Wenn Sie ein IQueryable mit mehreren BuildContainsExpression () verfeinern, wird es möglicherweise nicht in eine SQL-Anweisung umgewandelt, die am Ende wie erwartet ausgeführt wird.

Die Problemumgehung bestand für uns darin, mehrere LINQ-Joins zu verwenden, um einen einzigen SQL-Aufruf zu ermöglichen.

0
Shannon

Hier ist ein Beispiel, in dem ich demonstriere, wie setbasierte Abfragen mit dem DataServiceContext geschrieben werden: http://blogs.msdn.com/phaniraj/archive/2008/07/17/set-based-operations-in-ado -net-data-services.aspx

0
Phani Raj

Ich denke, ein Join in LINQ kann eine Umgehung sein.

Ich habe den Code jedoch nicht getestet. Ich hoffe es hilft. Prost. :-)

List<long?> txnIds = new List<long?>();
// Fill list 

var q = from t in svc.OpenTransaction
        join tID in txtIds on t equals tID
        select t;

In LINQ mitmachen:

http://weblogs.asp.net/salimfayad/archive/2008/07/09/linq-to-entities-join-queries.aspx

0
GabrielC