Как заставить Linq2Sql понимать пользовательские типы? - PullRequest
1 голос
/ 26 октября 2011

люди!

Предположим, у нас есть набор интерфейсов, представляющих проблемную область: IUser, IAddressBook, IComment и так далее. Предположим, что IUser определяется следующим образом:

public interface IUser : IAmIdentifiedEntity<int>, IHaveName
{
    string FullName { get; set; }
    string Email { get; set; }
    bool ReceiveNotification { get; set; }
}

public interface IHaveName
{
    string Name { get; set; }
}

В своем заявлении я использую только упомянутые контракты, например:

public IUser GetUser(string userName)
{
    return Warehouse.GetRepository<IUser>().GetAll()
        .First(u => u.Name == userName);
}

Как видите, я использую какой-то шлюз для получения данных. Метод репозитория GetAll() возвращает IQueryable<TEntity>, поэтому можно построить сложный запрос и использовать все преимущества отложенной загрузки. Когда я представлял его, я подумал об использовании Linq2Sql в будущем.

Некоторое время при разработке клиентского кода мы использовали реализацию хранения данных в памяти. Так что все работает отлично. Но теперь пришло время привязать домен к SQL Server. Итак, я начал внедрять всю инфраструктуру на Linq2Sql ... И когда сделал простое решение (вдохновленное статья Фредрика Калсета) и получил первое исключение, я понял, что все грусть этой идеи ... Вот воплощение IUser:

public partial class USER : IUser
{
    int IHaveID<int>.ID
    {
        get { return USERID; }
    }

    string IHaveName.Name
    {
        get { return USERNAME;  }
        set { USERNAME = value; }
    }

    string IUser.FullName 
    {
        get { return USERFULLNAME; }
        set { USERFULLNAME = value; }
    }

    // ... same approach for other properties
}

А здесь - исключение:

Exception: System.NotSupportedException: 
    The member 'Data.IHaveName.Name' has no supported translation to SQL.

Это очевидное исключение - провайдер Linq2Sql не понимает внешние интерфейсы, но как мне его разрешить? Я не могу изменить доменные интерфейсы для возврата Linq.Expression, например:

Expression<Func<string>> IHaveName.Name
{
    get { return (() => USERNAME);  }
    set { USERNAME = value(); }
}

потому что это нарушает все текущее использование кода интерфейсами домена и, кроме того, я не могу заменить int на Expression<Func<int>> из-за религиозных убеждений:)

Может быть, мне следует написать пользовательский посетитель Expression, чтобы пропустить запрос AST для запроса IUser.SomeProperty и заменить его внутренним суб-AST ...

Можете ли вы высказать свои мысли об этом?

ОБНОВЛЕНИЕ. Здесь я должен написать кое-что о реализации Repository. Посмотрите на GetAll() источник:

public IQueryable<TEntity> GetAll()
{
    ITable table = GetTable();
    return table.Cast<TEntity>();
}

protected ITable GetTable()
{
    return _dataContext.GetTable(_implType);
}

В моем примере TEntity <=> IUser и _implType <=> typeof(USER)

ОБНОВЛЕНИЕ 2. Я нашел связанный вопрос , где у OP аналогичная проблема: нужен какой-то мост между конкретными сущностями ORM и его доменными сущностями. И я нашел интересным ответ : автор предложил создать выражение для посетителя, которое будет выполнять преобразование между сущностями (домен ORM <=>).

Ответы [ 4 ]

0 голосов
/ 28 октября 2011

Кажется, я нашел способ решить мою проблему. Сегодня я обнаружил замечательную серию сообщений под названием «Запросы расширенной модели домена с использованием Linq». Автор представляет свой способ поддержки более удобного (симпатичного) синтаксиса Lambda для работы с сущностями. И он использует преобразования дерева выражений. Поэтому я буду следовать этому подходу, и если мне это удастся, я опубликую более подробный ответ (возможно, через мой блог).

Что вы думаете об этой идее? Стоит ли тратить время на внедрение поставщика Expression?

ОБНОВЛЕНИЕ. Я потерпел неудачу с пользовательским выражением Expression - требуется много преобразований через дерево выражений. Поэтому я решил переместить всю логику Linq в классы «хранилища», что включает в себя сложные запросы и управление постоянством.

0 голосов
/ 26 октября 2011

Это хорошо известная проблема, LINQ-to-SQL сама по себе выдает эту ошибку всякий раз, когда вы пытаетесь выполнить IQueryable с логикой, которую он не может преобразовать в строковую команду SQL.

Эта проблема также возникает из-заIQueryable - отложенное выполнение.

Вы можете принудительно выполнить IQueryable следующим образом:

public IUser GetUser(string userName)
{
    return Warehouse.GetRepository<IUser>().GetAll()
        .AsEnumerable()
        .First(u => u.Name == userName);
}

Это будет гарантировать, что ваши доменные интерфейсы могут оставаться как есть и не будут даватьу вас возникли проблемы при запуске либо.

ура!

0 голосов
/ 26 октября 2011

Вы пробовали использовать дженерики? Linq to SQL должен знать конкретный тип, чтобы он мог перевести ваше выражение в sql.

public TUser GetUser<TUser>(string userName) where TUser : IUser
{
    return Warehouse.GetRepository<TUser>().GetAll()
        .First(u => u.Name == userName);
}
0 голосов
/ 26 октября 2011

Укажите свойства в Name с атрибутами Column, так же, как и для любого другого типа, который вы используете с Linq2SQL.

...