Есть ли общий способ получить сущность Linq2SQL по его первичному ключу? - PullRequest
2 голосов
/ 18 июня 2009

Хорошо, короткая версия состоит в том, что у меня есть сущность 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 ... ИЛИ, в крайнем случае, мне нужно получить исходный объект и написать цикл, который копирует значение каждого свойства в одно, которое я получаю из контекста данных.

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

РЕДАКТИРОВАТЬ: Хорошо, посмотрел .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. Или, может быть, я перевернул аргументы ...)

Ответы [ 2 ]

3 голосов
/ 19 июня 2009

Я часто использую реализацию универсального репозитория от 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 source

2 голосов
/ 19 июня 2009

Попробуйте что-то вроде следующего, чтобы получить сущность по ID:

(, где 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));
    }

Это метод обновления (спасибо Marc Gravell ):

    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();
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...