Использование отражения для адресации свойства Linqed - PullRequest
1 голос
/ 26 апреля 2009

Я пытаюсь написать универсальный метод, который будет загружать запись определенного типа с определенным идентификатором. Вот один способ, который работает:

    public abstract class LinqedTable<T> where T : LinqableTable {
    public static T Get(long ID) {
        DataContext context = LinqUtils.GetDataContext<T>();
        var q = from obj in context.GetTable<T>()
                        where obj.ID == ID
                        select obj;
        return q.Single<T>();
    }
}

public abstract class LinqableTable {
    public abstract long ID { get; set; }
}

Вы можете игнорировать вызов LinqUtils.GetDataContext<T>(); это служебная функция, с которой мне приходится иметь дело с тем фактом, что в моей программе есть несколько контекстов данных. Дело в том, что теперь я могу объявить любой из моих классов подклассами LinqableTable и легко создать экземпляр записи этой таблицы, просто вызвав LinqedTable<MyType>.Get(ID).

Однако это имеет некоторые ограничения. Во-первых, это заставляет все мои таблицы иметь I поле идентификатора типа long с именем ID. Во-вторых, поскольку я использую абстрактный метод, я вынужден перейти к конструктору O / R и изменить свойство наследования каждого поля идентификатора в моей системе на «переопределить».

Я хочу больше гибкости, чем это. Естественно, я попробовал рефлексию и выдал следующее:

    public abstract class LinqedTable<T> where T : LinqableTable {
    public static T Get(long ID) {
        DataContext context = LinqUtils.GetDataContext<T>();
        var q = from obj in context.GetTable<T>()
                        where obj.IDValue == ID
                        select obj;
        return q.Single<T>();
    }
}

public abstract class LinqableTable {
    internal long IDValue {
        get { return (long)IDProperty.GetValue(this, null); }
        set { IDProperty.SetValue(this, value, null); }
    }
    internal PropertyInfo IDProperty {
        get { return this.GetType().GetProperty(IDPropertyName); }
    }
    internal protected virtual string IDPropertyName {
        get { return "ID"; }
    }
}

Теоретически, это позволяет мне переопределить имя столбца идентификатора, приведение к long должно быть в порядке с любым целым типом данных, и мне не нужно определять все мои столбцы идентификатора как overrides.

НО

Линку это не нравится. При вызове q.Single<T>(); я получаю ошибку времени выполнения:

The member 'EISS.Utils.LinqableTable.IDValue' has no supported translation to SQL.

Хорошо, сегодня я узнал, что Linq делает какую-то магию на заднем конце; он не создает экземпляр obj, а просто читает свойство IDValue. Должно быть, в свойстве IDValue должен быть установлен некоторый атрибут, который позволяет Linq делать свое дело.

а что?

Ответы [ 3 ]

3 голосов
/ 26 апреля 2009

Linq to SQL пытается преобразовать ваш linq-запрос в SQL, но не знает, как преобразовать ваше свойство в имя столбца в БД.

Хорошее объяснение можно найти здесь на SO: У простого linq to sql нет поддерживаемого перевода в SQL

Но как это решить, другое дело. Я с успехом использовал подход из этой темы:

http://social.msdn.microsoft.com/forums/en-US/linqprojectgeneral/thread/df9dba6e-4615-478d-9d8a-9fd80c941ea2/

Или вы можете использовать динамический запрос, как упомянуто здесь Скоттом Гатри:

http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

1 голос
/ 16 июля 2009

Прочитав эти посты: Общий доступ к данным с использованием LINQ to SQL и C # , LINQ-to-SQL: универсальная функция первичного ключа и Вызов универсального метода с типом

Мы с коллегой придумали следующий сборник:

Мы добавили следующий метод к нашему текстовому тексту (в частичном классе).

public T GetInstanceByPrimaryKey<T>(object primaryKeyValue) where T : class
{
    var table = this.GetTable<T>();
    var mapping = this.Mapping.GetTable(typeof(T));
    var pkfield = mapping.RowType.DataMembers.SingleOrDefault(d => d.IsPrimaryKey);

    if (pkfield == null)
        throw new Exception(String.Format("Table {0} does not contain a Primary Key field", mapping.TableName));

    var param = Expression.Parameter(typeof(T), "e");

    var predicate =
        Expression.Lambda<Func<T, bool>>(Expression.Equal(Expression.Property(param, pkfield.Name), Expression.Constant(primaryKeyValue)), param);

    return table.SingleOrDefault(predicate);
}

Тогда, где нам нужно создать экземпляр из имени типа и значения первичного ключа:

string name = "LinqObjectName";
int primaryKey = 123;

var dc = new YourDataContext();

Type dcType = dc.GetType();
Type type = dcType.Assembly.GetType(String.Format("{0}.{1}", dcType.Namespace, name));

MethodInfo methodInfoOfMethodToExcute = dc.GetType().GetMethod("GetInstanceByPrimaryKey");
MethodInfo methodInfoOfTypeToGet = methodInfoOfMethodToExcute.MakeGenericMethod(name);
var instance = methodInfoOfTypeToGet.Invoke(dc, new object[] { primaryKey });

return instance;

Надеюсь, это поможет!

0 голосов
/ 26 апреля 2009

Поскольку операторы LINQ, относящиеся к IQueryable LINQ-to-SQL, транслируются в запросы SQL, вам придется использовать расширение AsEnumerable (которое, в свою очередь, вызовет чтение всех элементов в базе данных) и выполнить отражение вещи на этом IEnumerable.
EDIT
Как требуется, вот пояснение
Как указано в комментарии, я имел в виду что-то вроде:

 (from obj in context.GetTable<T>() select obj).AsEnumerable().Where(x => x.IDValue == ID)

В отличие от запроса, выполняемого в IQueryable, который может быть отлично переведен в SQL, такой как


context.GetTable().Where(x => x.Text == "Hello")
который превращается в нечто похожее на
SELECT * FROM TABLE_MAPPED_TO_TYPE_T WHERE Text = 'Hello'

запрос, выполненный для IEnumerable - в вашем случае - будет выполнен путем извлечения всех записей вашей таблицы и последующего применения указанного фильтра с учетом кода.

...