Есть ли общий способ получить сущность Linq2SQL по его первичному ключу?

2

Хорошо, короткая версия заключается в том, что у меня есть объект Linq из LinqDataSourceUpdateEventArgs, и мне нужно обрабатывать обновление вручную, потому что MS была глупой.

Я могу получить объект Table из контекста данных, что позволяет мне:

var newObj = e.NewObject;

var table = FormContext.GetTable(e.NewObject.GetType());

table.Attach(newObj, e.OriginalObject);

if (BuildingObject != null)
    BuildingObject(sender, new HeirarchicalBuildObjectEventArgs(newObj));


FormContext.SubmitChanges();

К сожалению, я получаю исключение "Невозможно добавить объект с уже существующим ключом".

Конечно, забавная часть: я получаю это в FormContext.SubmitChanges(), а не на table.Attach()... что не имеет для меня никакого смысла, но что бы то ни было.

Я думаю, что мне нужно фактически получить объект из контекста и прикрепить его, а не e.OriginalObject... ИЛИ, в крайнем случае, мне нужно получить исходный объект и написать цикл, который копирует значение каждого свойства в том, которое я получаю из контекста данных.

В любом случае, мне нужно искать объект по его первичному ключу, не зная тип объекта. Есть ли способ сделать это?

EDIT: Хорошо, просмотрел .NET Reflector, и я замечаю, что, среди прочего, LinqDataSourceView присоединяет OLD-объект данных, а затем копирует все значения в него... но это, очевидно, пропускает ассоциации. Попытаемся прикрепить старый объект и скопировать значения, думаю...

Действительно смешная часть? Я написал функцию для копирования свойств из одного экземпляра объекта в другой давным-давно, и он содержит этот комментарий:

//Мы не можем копировать ассоциации и, вероятно, не должны

Иногда мне хотелось бы, чтобы мои комментарии были более подробными...

ИЗМЕНИТЬ РЕДАКТИРОВАТЬ: Хорошо, так что правильный ответ: я задал неправильный вопрос!

Правильный код:

        var newObj = e.NewObject;

        var table = FormContext.GetTable(e.NewObject.GetType());

        if (BuildingObject != null)
            BuildingObject(sender, new HeirarchicalBuildObjectEventArgs(newObj));

        table.Attach(newObj, e.OriginalObject);

        FormContext.SubmitChanges();


        e.Cancel = true;

Я изначально пытался подключиться после BuildingObject, но получил другую ошибку и переместил инструкцию attach, чтобы исправить ее. (Я думаю, потому что я вызывал неправильную версию Attach. Или, может быть, у меня изменились аргументы...)

  • 0
    Как вы можете искать сущность, если вы не знаете ее тип?
  • 0
    Я могу получить таблицу, не зная типа. Где-то там Linq2SQL знает, что что-то помечено как первичный ключ ... Мне не нужно знать, тем более, что у меня есть мелкая копия объекта, который я мог бы использовать, чтобы найти его. Фактически, таким вещам, как LinqDataSource, каким-то образом удается это сделать ... в противном случае они не смогут выполнить поведение, которое я пытаюсь переписать.
Теги:
linq-to-sql

2 ответа

3
Лучший ответ

Я часто использую реализацию универсального репозитория от Sutekishop, веб-магазина электронной коммерции с открытым исходным кодом, созданного с помощью asp.net mvc и L2S.
Он имеет хороший GetByID для общего типа T, который полагается на атрибуты L2S для классов модели. Это та часть, которая выполняет эту работу:

public virtual T GetById(int id)
{
    var itemParameter = Expression.Parameter(typeof(T), "item");

    var whereExpression = Expression.Lambda<Func<T, bool>>
        (
        Expression.Equal(
            Expression.Property(
                itemParameter,
                typeof(T).GetPrimaryKey().Name
                ),
            Expression.Constant(id)
            ),
        new[] { itemParameter }
        );
     return GetAll().Where(whereExpression).Single();
}

и метод расширения, который ищет свойство первичного ключа; как вы можете видеть, он ожидает атрибут "Столбец" с "IsPrimaryKey" в свойстве класса. Методы расширения:

public static PropertyInfo GetPrimaryKey(this Type entityType) {
    foreach (PropertyInfo property in entityType.GetProperties()) {
        if (property.IsPrimaryKey()) {
            if (property.PropertyType != typeof (int)) {
                throw new ApplicationException(string.Format("Primary key, '{0}', of type '{1}' is not int", property.Name, entityType));
            }
            return property;
        }
    }
    throw new ApplicationException(string.Format("No primary key defined for type {0}", entityType.Name));
} 

public static TAttribute GetAttributeOf<TAttribute>(this PropertyInfo propertyInfo) {
    object[] attributes = propertyInfo.GetCustomAttributes(typeof(TAttribute), true);
    if (attributes.Length == 0)
        return default(TAttribute);
    return (TAttribute)attributes[0];
}

public static bool IsPrimaryKey(this PropertyInfo propertyInfo) {
    var columnAttribute = propertyInfo.GetAttributeOf<ColumnAttribute>();
    if (columnAttribute == null) return false;
    return columnAttribute.IsPrimaryKey;
}

Все кредиты для этого кода отправляются Майк Хэдлоу! Целую реализацию можно найти в источник sutekishop

  • 0
    Я даю вам ответ, потому что это похоже на то, что сработало бы для первоначального вопроса, если бы я справился с его реализацией до того, как понял свою настоящую проблему. Технически у меня не было бы идентификатора, но я уверен, что смогу использовать эти функции, чтобы получить его ...
  • 0
    ну да, я не совсем понял ваш вопрос, поэтому я сосредоточился на заголовке. Если надеюсь, по крайней мере, этот код поможет когда-нибудь!
Показать ещё 1 комментарий
2

Попробуйте следующее: Получить объект по идентификатору:

(Где TLinqEntity - тип класса, который генерируется LinqToSql... и является общим параметром в самом классе.)

    protected TLinqEntity GetByID(object id, DataContext dataContextInstance)
    {
        return dataContextInstance.GetTable<TLinqEntity>()
            .SingleOrDefault(GetIDWhereExpression(id));
    }

    static Expression<Func<TLinqEntity, bool>> GetIDWhereExpression(object id)
    {
        var itemParameter = Expression.Parameter(typeof(TLinqEntity), "item");
        return Expression.Lambda<Func<TLinqEntity, bool>>
            (
            Expression.Equal(
                Expression.Property(
                    itemParameter,
                    TypeExtensions.GetPrimaryKey(typeof(TLinqEntity)).Name
                    ),
                Expression.Constant(id)
                ),
            new[] { itemParameter }
            );
    }

    static PropertyInfo GetPrimaryKey(Type entityType)
    {
        foreach (PropertyInfo property in entityType.GetProperties())
        {
            var attributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), true);
            if (attributes.Length == 1)
            {
                ColumnAttribute columnAttribute = attributes[0];
                if (columnAttribute.IsPrimaryKey)
                {
                    if (property.PropertyType != typeof(int))
                    {
                        throw new ApplicationException(string.Format("Primary key, '{0}', of type '{1}' is not int",
                            property.Name, entityType));
                    }
                    return property;
                }
            }
        }
        throw new ApplicationException(string.Format("No primary key defined for type {0}", entityType.Name));
    }

Это метод обновления (спасибо Марк Гравелл):

    public virtual void Update(DataContext dataContext, TLinqEntity obj)
    {
        // get the row from the database using the meta-model
        MetaType meta = dataContext.Mapping.GetTable(typeof(TLinqEntity)).RowType;
        if (meta.IdentityMembers.Count != 1)
            throw new InvalidOperationException("Composite identity not supported");
        string idName = meta.IdentityMembers[0].Member.Name;
        var id = obj.GetType().GetProperty(idName).GetValue(obj, null);

        var param = Expression.Parameter(typeof(TLinqEntity), "row");
        var lambda = Expression.Lambda<Func<TLinqEntity, bool>>(
            Expression.Equal(
                Expression.PropertyOrField(param, idName),
                Expression.Constant(id, typeof(int))), param);

        object dbRow = dataContext.GetTable<TLinqEntity>().Single(lambda);

        foreach (MetaDataMember member in meta.DataMembers)
        {
            // don't copy ID or timstamp/rowversion
            if (member.IsPrimaryKey || member.IsVersion) continue;
            // (perhaps exclude associations too)

            member.MemberAccessor.SetBoxedValue(
                ref dbRow, member.MemberAccessor.GetBoxedValue(obj));
        }
        dataContext.SubmitChanges();
    }
  • 0
    Это может сработать, но я бы действительно не хотел возвращаться и начинать добавлять вещи в каждый класс, с которым мы решили использовать эту технику ... и действительно, если бы я собирался это сделать, я бы просто создал интерфейс, который требует конкретный элемент данных для получения первичного ключа.
  • 0
    Это работает, потому что я использую его для каждого проекта, который я использую при реализации Linq2Sql
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню