是否有通用的方法来获取Linq2SQL实体的主键?

时间:2009-06-18 19:12:07

标签: c# .net asp.net linq-to-sql

好的,简短的版本是我有一个来自LinqDataSourceUpdateEventArgs的Linq实体,我需要手动处理更新,因为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之后附加,但是遇到了一些其他错误并移动了附加语句以便纠正它。 (我想是因为我正在调用错误版本的附件。或许我的论点被颠倒了......)

2 个答案:

答案 0 :(得分:3)

我经常使用Sutekishop的通用存储库实现,这是一个用asp.net mvc和L2S构建的开源电子商务网站。
它对于泛型类型T有很好的GetByID,它依赖于模型类的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();
}

和查找主键属性的扩展方法;正如你所看到的那样,它期望“Class”属性在类属性上带有“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;
}

此代码的所有信用均转至Mike Hadlow!整个实施可以在sutekishop source

中找到

答案 1 :(得分:2)

尝试使用以下内容按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();
    }