Giải pháp thay thế 'Chứa ()' bằng cách sử dụng Linq cho Thực thể?


86

Tôi đang cố gắng tạo một truy vấn sử dụng danh sách id trong mệnh đề where, sử dụng api máy khách Silverlight ADO.Net Data Services (và do đó là Linq To Entities). Có ai biết giải pháp thay thế cho Chứa không được hỗ trợ không?

Tôi muốn làm một cái gì đó như thế này:

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

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

Đã thử cái này:

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

Nhưng có "Phương pháp 'Bất kỳ' không được hỗ trợ".


35
Lưu ý: Entity Framework 4 (trong .NET 4) có phương thức "Chứa", đề phòng trường hợp ai đó tình cờ đọc được điều này mà không biết về nó. Tôi biết OP đang sử dụng EF1 (.NET 3.5).
DarrellNorton

7
@Darrell Tôi vừa lãng phí nửa giờ vì bỏ qua bình luận của bạn. Tôi ước tôi có thể làm cho nhận xét của bạn nhấp nháy và di chuyển trên màn hình.
Chris Dwyer

Câu trả lời:


97

Cập nhật: EF ≥ 4 hỗ trợ Containstrực tiếp (Checkout Any), vì vậy bạn không cần bất kỳ giải pháp thay thế nào.

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

SỬ DỤNG:

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

6
Cảnh báo; khi đối số là bộ sưu tập lớn (của tôi là 8500 mục int list), tràn ngăn xếp. Bạn có thể nghĩ thật điên rồ khi vượt qua một danh sách như vậy, nhưng tôi nghĩ rằng điều này cho thấy một lỗ hổng trong cách tiếp cận này.
dudeNumber 4

2
Đúng nếu tôi đã sai lầm. nhưng điều này có nghĩa là khi bộ sưu tập được truyền (bộ lọc) là một tập hợp rỗng, về cơ bản nó sẽ dẫn đến tất cả dữ liệu vì nó chỉ trả về tham số truy vấn. Tôi đã mong đợi nó lọc tất cả giá trị, có cách nào để làm điều này không?
Ngủ trưa

1
Nếu ý của bạn là khi bộ sưu tập kiểm tra trống, nó sẽ không trả lại kết quả nào, thì trong đoạn mã trên thay if (!collection.Any()) //action;thế hành động - thay thế bằng cách chỉ trả lại truy vấn trống thuộc loại được yêu cầu để có hiệu suất tốt nhất - hoặc chỉ cần xóa dòng này.
Shimmy Weitzhandler

1
return WhereIn (truy vấn, bộ chọn, bộ sưu tập); nên được thay thế bằng tập hợp return WhereIn (query, selector, (IEnumerable <TValue>)); để tránh đệ quy không mong muốn.
Antoine Aubry

1
Tôi tin rằng có một lỗi trong mã. Nếu danh sách các giá trị được cung cấp trống, hành vi đúng là không trả về kết quả - tức là / không có đối tượng nào trong truy vấn tồn tại trong bộ sưu tập. Tuy nhiên, mã hoàn toàn ngược lại - tất cả các giá trị được trả về, không phải không có giá trị nào trong số chúng. Tôi tin rằng bạn muốn truy vấn "if (! Collection.Any ()) return.Where (e => false)"
ShadowChaser 29/02/12

18

Bạn có thể tự viết mã một số e-sql (lưu ý từ khóa "it"):

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

Đây là mã mà tôi đã sử dụng để tạo một số e-sql từ một bộ sưu tập, YMMV:

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

1
Bạn có thêm thông tin nào về "nó" không? Tiền tố "it" hiển thị trong các mẫu MSDN, nhưng tôi không thể tìm thấy lời giải thích khi nào / tại sao cần "it".
Robert Claypool

1
Được sử dụng trong truy vấn động Entity Framework, hãy xem geekswithblogs.net/thanigai/archive/2009/04/29/… , Thanigainathan Siranjeevi giải thích ở đó.
Shimmy Weitzhandler

13

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

và truy vấn trở thành:

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

3
Nếu bạn muốn làm một 'Không chứa', chỉ cần thực hiện chỉnh sửa sau trong phương pháp BuildContainsExpression: - Expression.Equal trở thành Expression.NotEqual - Expression.Or trở thành Expression.And
Merritt

2

Tôi không chắc về Silverligth, nhưng trong linq tới các đối tượng, tôi luôn sử dụng any () cho các truy vấn này.

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

5
Bất kỳ không lấy một đối tượng của kiểu trình tự - nó hoặc không có tham số (trong trường hợp đó, nó chỉ là "cái này trống hay không") hoặc nó có một vị từ.
Jon Skeet

Tôi thực sự vui mừng vì đã tìm thấy câu trả lời này:) +1 Cảm ơn AndreasN
SDReyes

1

Để hoàn thành bản ghi, đây là mã cuối cùng tôi đã sử dụng (đã bỏ qua kiểm tra lỗi để rõ ràng) ...

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


0

Cảm ơn rất nhiều. Đối với tôi, phương pháp mở rộng WhereIn là đủ. Tôi đã cấu hình nó và tạo cùng một lệnh SQL cho DataBase dưới dạng e-sql.

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

Đã tạo ra cái này:

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

Xin lỗi người dùng mới, tôi đã nhận xét về câu trả lời thực tế, nhưng có vẻ như tôi chưa thể làm điều đó?

Dù sao, liên quan đến câu trả lời với mã mẫu cho BuildContainsExpression (), hãy lưu ý rằng nếu bạn sử dụng phương thức đó trên các Thực thể cơ sở dữ liệu (tức là không phải các đối tượng trong bộ nhớ) và bạn đang sử dụng IQueryable, thì nó thực sự phải chuyển đến cơ sở dữ liệu vì về cơ bản nó thực hiện rất nhiều điều kiện SQL "hoặc" để kiểm tra mệnh đề "where in" (chạy nó với SQL Profiler để xem).

Điều này có nghĩa là, nếu bạn đang tinh chỉnh một IQueryable với nhiều BuildContainsExpression (), nó sẽ không chuyển nó thành một câu lệnh SQL được chạy ở cuối như bạn mong đợi.

Giải pháp cho chúng tôi là sử dụng nhiều phép nối LINQ để giữ nó cho một lệnh gọi SQL.


0

Ngoài câu trả lời đã chọn.

Thay thế Expression.Orbằng Expression.OrElseđể sử dụng với Nhibernate và sửa lỗi Unable to cast object of type 'NHibernate.Hql.Ast.HqlBitwiseOr' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'ngoại lệ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.