2015-09-25 9 views
3

Собственно, мой коллега задал мне этот вопрос, и я не смог ответить на него. Вот оно.LINQ to Entities. Содержит кортеж (2 внешних ключа)

Учитывая сущность с 2 внешними ключами, скажем

public class MyTable 
{ 
    public int Key1 { get; set; } 
    public int Key2 { get; set; } 
} 

и 2 перечислены

public ICollection List1 => new List<int> { 1, 2, 3 }; 
public ICollection List2 => new List<int> { 4, 5, 6 }; 

он должен запросить для всех записей, где Key1 совпадает со значением из List1 и Key2 соответствует значению из Список2, например

Key1 == 1 && Key2 == 4 

то есть, он хочет, чтобы проверить для любого заданного набора из List1 и List2, (1, 4), (2, 5) и (3, 6).

Есть ли простой способ в EF для этого?

+0

Что касается (1, 5), вы тоже ищете эти случаи? –

+0

Нет, это то, что делает все это настолько сложным. –

+0

Может ли список иметь повторяющиеся номера? – DarkKnight

ответ

1

Вы можете сделать for петлю, чтобы захватить некоторую локальную переменную (в каждом цикле) в Where и использовать Concat (или Union - может быть, тем хуже результаты) просуммировать до всего результат, как это:

IQueryable<MyTable> q = null; 
//suppose 2 lists have the same count 
for(var i = 0; i < list1.Count; i++){ 
    var k1 = list1[i]; 
    var k2 = list2[i]; 
    var s = context.myTables.Where(e => e.Key1 == k1 && e.Key2 == k2); 
    q = q == null ? s : q.Concat(s); 
} 
//materialize the result 
var result = q.ToList(); 

ПРИМЕЧАНИЕ: здесь можно использовать Concat, потому что каждый побочный результат должен быть уникальным (на основе поиска ключей). Он, безусловно, имеет лучшую производительность, чем Union (гарантируя уникальность, хотя мы уже знаем, что подвыборы все уникальны заранее - так что это не нужно).

Если у вас есть список int (только целочисленных), вы также можете соединить ключи в подчёркивании разделена строки и использовать Contains обычно так:

var stringKeys = list1.Select((e,i) => e + "_" + list2[i]).ToList(); 
var result = context.myTables.Where(e => stringKeys.Contains(e.Key1 + "_" + e.Key2)) 
      .ToList(); 

Построения дерева выражений и другой подход, но это сложнее, хотя я не уверен, что он имеет лучшую производительность.

+1

Это решение действительно работает, поэтому мы можем использовать этот подход на данный момент. –

+0

@Aredhel это самое легкое решение, о котором я могу думать. Выбор его или дерева выражений - еще одна проблема, которая лучше работает (как я уже сказал, я не уверен в этом). Выражение выдает запрос типа SELECT ... WHERE boo1 ИЛИ boo2 OR ... ', в то время как первый код в моем ответе (с' Concat') выдает запрос типа 'SELECT ... WHERE boo1 UNION ALL SELECT. WHERE boo2 ... '. Также ответ с деревом выражений (размещен здесь) неверен, должны быть некоторые выражения OR, но он упорядочил все условия WHERE, которые никогда не будут истинными (всегда результат пуст). – Hopeless

0

Подтвержденные для работы с каркасом Entity

var tuples = List1.Cast<int>().Zip(List2.Cast<int>(), (l, r) => new { l, r }); 
var results = Orders.Where(o => 
    tuples.Contains(new { l = (int)o.KeyOne, r = (int)o.KeyTwo }) 
); 

Или проще, если вы определяете свои списки, как ICollection<int> или IList<int> (и т.д ...):

var tuples = List1.Zip(List2, (l, r) => new { l, r }); 
var results = Orders.Where(o => 
    tuples.Contains(new { l = (int)o.KeyOne, r = (int)o.KeyTwo }) 
); 
+1

Это будет работать с LINQ to Objects, но вы уверены, что оно будет работать для LINQ для Entities? Помните, что запрос LINQ to Entities должен быть преобразован в SQL-код, который может быть запущен в базе данных. Я не говорю, что ваше решение не сработает, но я не уверен, что так оно и будет. – jmcilhinney

+0

@jmcilhinney Ты прав, мой плохой. Я обновил код и протестировал его (определенно работает на этот раз :) – Rob

+1

Если я не ошибаюсь, изменение от 'ICollection' до' IList' не устраняет необходимость в вызове 'Cast'. 'Zip' является расширением' IEnumerable ', поэтому важно то, что интерфейс является общим, а' IList' - нет. – jmcilhinney

1

Попробуйте это:

static IQueryable<TSource> WhereIn(this Table<TSource> table, List<object[]> list) where TSource : class 
{ 
    var query = table.AsQueryable(); 
    foreach (object[] item in list) 
    { 
     Expression<Func<TSource, bool>> expr = WhereInExpression(item); 
     query = query.Where(expr); 
    } 

    return query; 
} 

static Expression<Func<TSource, bool>> WhereInExpression<TSource>(object[] item) 
{ 

    ParameterExpression parameterItem = Expression.Parameter(typeof(TSource), "expr"); 
    BinaryExpression filter1 = Expression.Equal(LambdaExpression.PropertyOrField(parameterItem, "Key1"), 
    Expression.Constant(item[0])); 

    BinaryExpression filter2 = Expression.Equal(LambdaExpression.PropertyOrField(parameterItem, "Key2"), 

    Expression.Constant(item[1])); 

    BinaryExpression filter = LambdaExpression.And(filter1, filter2); 

    var expr = Expression.Lambda<Func<TSource, bool>>(filter, new ParameterExpression[] { parameterItem }); 

    expr.Compile(); 

    return expr; 
} 

Использование:

List<object[]> list = new List<object[]>() { new object[] { 1, 100 }, new object[] { 1, 101 }, new object[] { 2, 100 } }; 

var result = db.MyTable.WhereIn<MyTable>(list); 
+0

Мы хотели бы сделать это, к сожалению, клавиши типа короткие? в модели (а не в нашем случае, ум), и мы не можем преодолеть это исключение: «Бинарный оператор Equal не определен для типов« System.Nullable'1 [System.Int16] »и« System.Int16 ». " –

+0

Если они являются нулевыми, они не являются ключами ... В любом случае вы всегда можете добавить проверку для '.HasValue' перед' .Equals' – Basic

+0

+1 для предложения построить дерево выражений, это то, что пригодится дальше по дороге. –

-1

Fiddle здесь: https://dotnetfiddle.net/YyyZBY

var List_1 = new List<int> { 1, 2, 3 }; 
    var List_2 = new List<int> { 4, 5, 6 }; 

    var TargetList = new List<MyTable>(); 

    var index1=0; 
    var List1 = List_1.Select(x=>new { ind=index1++,value=x }); 
    var index2=0; 
    var List2 = List_2.Select(x=>new { ind=index2++,value=x }); 

    var values = from l1 in List1 
       join l2 in List2 on l1.ind equals l2.ind 
       select new {value1=l1.value,value2=l2.value }; 


var result = TargetList.Where(x=>values.Any(y=>y.value1==x.Key1&&y.value2==x.Key2)); 
+0

Это также дает мне «Невозможно создать постоянное значение типа« Анонимный тип ». В этом контексте поддерживаются только примитивные типы или типы перечислений». –

+0

@Aredhel: он работает .. пожалуйста, проверьте скрипт в ответ. – DarkKnight

+1

Да, это так, но для LINQ только для объектов. Сбой для LINQ to Entities локально. –