简体   繁体   English

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

[英]Is there a generic way to get a Linq2SQL Entity by its primary key?

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. 编辑:好的,通过.NET Reflector看了一下,我注意到,除其他外,LinqDataSourceView附加了OLD数据对象,然后将所有值复制到其中...但是显然跳过了关联。 Going to try attaching the old object and copying values over, I guess... 我想尝试附加旧对象并复制值。

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. 我最初尝试在BuildingObject之后进行附加,但是遇到了其他错误并移动了attach语句以进行更正。 (I think because I was calling the wrong version of Attach. Or maybe I had the arguments reversed...) (我想是因为我打错了Attach的版本。或者也许我把论点颠倒了……)

I often use implementation of generic repository from Sutekishop, open source e-commerce web shop built with asp.net mvc and L2S. 我经常使用来自Sutekishop的通用存储库的实现,Sutekishop是使用asp.net mvc和L2S构建的开源电子商务网站。
It has nice GetByID for generic type T, which relies on L2S attributes on model classes. 对于通用类型T,它具有不错的GetByID,它依赖于模型类上的L2S属性。 This is the part that does the job: 这是完成工作的部分:

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. 如您所见,它期望class属性上带有“ IsPrimaryKey”的“ Column”属性。 Extension methods: 扩展方法:

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 ! 此代码的全部功劳归Mike Hadlow所有 Whole implementation can be found in sutekishop source 完整的实现可以在sutekishop源代码中找到

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM