Использование первичных ключей в linqtoSQL - PullRequest
3 голосов
/ 06 февраля 2012

Я пытаюсь написать какой-нибудь универсальный linqtoSQL, который выполняет операции над объектами на основе их первичных ключей. Некоторые сущности, с которыми я работаю, имеют составные первичные ключи.

По сути, я хочу иметь возможность делать такие вещи, как:

if( PrimaryKey(foo) == PrimaryKey(bar) ) ...

или

from oldEntity in oldTableOrCollection
join newEntity in newTableOrCollection
on PrimaryKeyOf(oldEntity) equals PrimaryKeyOf(newEntity)
select new {oldEntity, newEntity}

только с требованием, чтобы у сущностей были первичные ключи.

Вот что я нашел до сих пор:

Можно делать такие вещи, как

var query = from row in oneTableOfRows
            join otherRow in anotherTableOfRows on
            new { row.Column1, row.Column2 }
            equals
            new { otherRow.Column1, otherRow.Column2}
            select ...;

, в котором linqtosql будет использовать анонимные типы для сравнения (при условии совпадения имен свойств).

Затем я могу абстрагировать метод выбора столбцов, к которым нужно присоединиться, чтобы разрешить общие типы строк:

void DoQuery<RowType,KeyType>(Table<RowType> oneTableOfRows,
                         Table<RowType> anotherTableOfRows,
                         Func<RowType, KeyType> keySelector)
{
    var query = from row in oneTableOfRow
            join otherRow in anotherTableOfRows on
            keySelector(row)
            equals
            keySelector(otherRow)
            select ...;
}

...

Func<rowType, keyType> myKeySelector = row => new { row.Column1, row.Column2 };

DoQuery(table, otherTable, myKeySelector);

Сейчас я пытаюсь перейти к тому, где keySelector выберет первичный ключ любого RowType. Я использую пользовательский шаблон на основе http://www.codeplex.com/l2st4 для создания моих сущностей (что само по себе довольно похоже на значение по умолчанию в VS). Я надеюсь в идеале дать каждому сгенерированному RowType возможность выбрать свой первичный ключ, полученный из dbml. Пока выход выглядит следующим образом:

public interface IPrimaryKeyEntity<PrimaryKeyType>
{
    PrimaryKeyType PrimaryKey { get; }
}

//Here, Product is a table with composite primary key based on its ProductID and it's ProductPriceVersionID columns

//I've tried using both class and struct as the primary key type for entities
public class ProductPrimaryKey
{
    public Guid ProductID;
    public Guid ProductPriceVersionID;
}

public partial class Product : IPrimaryKeyEntity<ProductPrimaryKey>,
                               INotifyPropertyChanging, INotifyPropertyChanged
{
    #region IPrimaryKeyEntity Implementation
public ProductPrimaryKey PrimaryKey
    {
        get
        { return new ProductPrimaryKey()
                     {
                         ProductID = this.ProductID,
                         ProductPriceVersionID = this.ProductPriceVersionID
                     };
        }
    }
#endregion

    ...
    //Rest is mostly standard LinqToSql entity generation
}

Возвращаясь к исходной цели, теперь я могу скомпилировать следующее для всех моих первичных ключевых сущностей:

from oldEntity in oldTableOrCollection
join newEntity in newTableOrCollection
on oldEntity.PrimaryKey equals newEntity.PrimaryKey
select new {oldEntity, newEntity}

Однако, во время выполнения, я получаю печально известное исключение [PrimaryKey] "не поддерживает перевод в SQL".

Я понимаю, что для перевода на SQL необходимо использовать Expression, однако я довольно незнаком с Linq.Expressions и пока не достиг прогресса, пытаясь применить его к моей ситуации ...

Если кто-то может улучшить то, что у меня есть, или у него есть лучший метод в целом, я был бы рад узнать ....

Приветствия

1 Ответ

2 голосов
/ 07 февраля 2012

Проблема в том, что вы вызываете функцию здесь :

join ... on oldEntity. PrimaryKey равно newEntity. PrimaryKey

Свойство-get - это вызов функции.Linq to SQL не знает этой функции, поэтому не может перевести ее.

Единственный способ выполнить эту работу - создать выражение, соответствующее этому SQL:

        join otherRow in anotherTableOfRows on
        new { row.Column1, row.Column2 }
        equals
        new { otherRow.Column1, otherRow.Column2}

НетИначе я вас уверяю.Вы можете сделать это следующим образом: во-первых, вы объявляете собственный класс кортежа:

class CustomTuple2<T1,T2>
{
 public T1 Item1 { get; set; }
 public T2 Item2 { get; set; }
}

Вы делаете это для всех возможных подсчетов членов (например, 8 или 16).

Давайте посмотрим накак переводится соединение:

IQueryable<T1> t1 = ...; //table 1
IQueryable<T2> t2 = ...; //table 2
var joined = t1.Join(t2, _t1 => _t1.Key, _t2 => _t2.Key);

Параметры лямбда-выражения являются выражениями. Вам нужно построить эти два выражения, используя деревья выражений. Это волшебство!

Например, первая лямбда:

var param = Expression.Parameter(typeof(T1), "_t1");
var key = Expression.Property(param, "Key");
var lambda1 = Expression.Lambda(param, key);

Сделайте то же самое для второголямбда.(Помните, что это был псевдокод. Я точно не помню, как все это называлось.)

Наконец, вызовите Join:

var joined = typeof(Queryable).GetMethod("Join").Invoke(t1, t2, lambda1, lambda2);

Так вот как вы строитеприсоединиться во время выполнения!Обратите внимание, что все это был псевдокод.Потребуются некоторые исследования, чтобы понять API и заставить его работать, но вы наверняка сможете это сделать.

Кроме того, если у вас есть составной ключ, вам понадобится упомянутый класс кортежа для проецирования ключевых членов.в.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...