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

时间:2021-10-18 12:09:47

Ok, the short version is that I have a Linq entity from a LinqDataSourceUpdateEventArgs and I need to handle the Update manually because MS was stupid.

好的,简短的版本是我有一个来自LinqDataSourceUpdateEventArgs的Linq实体,我需要手动处理更新,因为MS是愚蠢的。

I can get the Table object from the data context, which allows me to, well, this:

我可以从数据上下文中获取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();

Unfortunately, I get the exception "Cannot add an entity with a key that is already in use."

不幸的是,我得到了一个例外“无法添加一个已经在使用的密钥的实体”。

Of course, the funny part is I get that on FormContext.SubmitChanges(), NOT on table.Attach()... which makes no sense to me, but whatever.

当然,有趣的部分是我在FormContext.SubmitChanges()上得到它,而不是在table.Attach()...这对我没有意义,但无论如何。

I'm thinking I need to actually get the object from the context, and attach using that instead of e.OriginalObject... OR, as a last resort, I need to get the original object and write a loop which copies the value of every property into the one I get from the data context.

我想我需要从上下文中实际获取对象,并使用它而不是e.OriginalObject附加...或者,作为最后的手段,我需要获取原始对象并编写一个复制值的循环我从数据上下文得到的每个属性。

Either way, I need to look up an object by it's primary key without knowing the type of the object. Is there a way to do that?

无论哪种方式,我都需要通过它的主键查找对象而不知道对象的类型。有没有办法做到这一点?

EDIT: Ok, took a look through .NET Reflector and I'm noticing that, among other things, LinqDataSourceView attaches the OLD data object and then copies all the values into it... but that apparently skips the associations. Going to try attaching the old object and copying values over, I guess...

编辑:好的,看看.NET Reflector,我注意到,除其他外,LinqDataSourceView附加了OLD数据对象,然后将所有值复制到其中......但显然会跳过关联。我想尝试附加旧对象并复制值,...

The really funny part? I wrote a function to copy properties from one entity instance to another a long time ago, and it contains this comment:

真有趣的部分?我写了一个函数,很久以前将属性从一个实体实例复制到另一个实体,它包含这个注释:

//We can't copy associations, and probably shouldn't

//我们不能复制关联,也许不应该复制关联

Sometimes I wish my comments were more thorough...

有时我希望我的评论更加彻底......

EDIT EDIT: Ok, so once again the correct answer is: I asked the wrong question!

编辑编辑:好的,所以再一次正确答案是:我问了错误的问题!

The correct code is:

正确的代码是:

        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;

I originally was trying to attach after BuildingObject, but got some other error and moved the attach statement in an effort to correct it. (I think because I was calling the wrong version of Attach. Or maybe I had the arguments reversed...)

我最初试图在BuildingObject之后附加,但得到了一些其他错误并移动了附加语句以努力纠正它。 (我想是因为我正在调用错误版本的Attach。或者我可能反驳了论点......)

2 个解决方案

#1


I often use implementation of generic repository from Sutekishop, open source e-commerce web shop built with asp.net mvc and L2S.
It has nice GetByID for generic type T, which relies on L2S attributes on model classes. This is the part that does the job:

我经常使用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();
}

and extension method that looks for primary key property; as you can see it expects "Column" attribute with "IsPrimaryKey" on class property. Extension methods:

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

All credits for this code goes to Mike Hadlow! Whole implementation can be found in sutekishop source

此代码的所有学分都归Mike Hadlow所有!整个实现可以在sutekishop源中找到

#2


Try something like the following to get the Entity by ID:

尝试使用以下内容按ID获取实体:

(Where TLinqEntity is the type of class that is generated by LinqToSql...and is a Generic Parameter in the class itself.)

(其中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));
    }

This is the Update Method (thanks to Marc Gravell):

这是更新方法(感谢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();
    }

#1


I often use implementation of generic repository from Sutekishop, open source e-commerce web shop built with asp.net mvc and L2S.
It has nice GetByID for generic type T, which relies on L2S attributes on model classes. This is the part that does the job:

我经常使用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();
}

and extension method that looks for primary key property; as you can see it expects "Column" attribute with "IsPrimaryKey" on class property. Extension methods:

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

All credits for this code goes to Mike Hadlow! Whole implementation can be found in sutekishop source

此代码的所有学分都归Mike Hadlow所有!整个实现可以在sutekishop源中找到

#2


Try something like the following to get the Entity by ID:

尝试使用以下内容按ID获取实体:

(Where TLinqEntity is the type of class that is generated by LinqToSql...and is a Generic Parameter in the class itself.)

(其中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));
    }

This is the Update Method (thanks to Marc Gravell):

这是更新方法(感谢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();
    }