Хорошо, короткая версия заключается в том, что у меня есть объект 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. Или, может быть, у меня изменились аргументы...)
Я часто использую реализацию универсального репозитория от 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
Попробуйте следующее: Получить объект по идентификатору:
(Где 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();
}