按接口中定义的字段查询

时间:2013-07-01 12:30:03

标签: c# generics linq-to-sql

我有几个实现ISortable接口的类:

public interface ISortable
{
    int Id { get; set; }
    int? Idx { get; set; }
}

在我的DbContext类中,我有一个更新方法,应该为实现ISortable的实体做一些额外的事情:

public void UpdateSingle<T>(T item) where T : class
{
    // If entity is Sortable, update the indexes of the records between the new and the old index of the updated entity
    var sortable = item as ISortable;
    if (sortable != null)
    {
        Detach(item);   // need to detach the entity from the context in order to retrieve the old values from DB
        var oldItem = Find<T>(sortable.Id) as ISortable;

        if (oldItem != null && sortable.Idx != oldItem.Idx)
        {
            var entities = FindAll<T>().ToList().Cast<ISortable>();

            var oldIdx = oldItem.Idx;
            var newIdx = sortable.Idx;

            if (newIdx > oldIdx)
            {
                var toUpdate = entities.Where(a => a.Idx <= newIdx && a.Idx > oldIdx).Select(a => a);
                foreach (var toUpdateEntity in toUpdate)
                {
                    toUpdateEntity.Idx = toUpdateEntity.Idx - 1;
                }
            }
            else
            {
                var toUpdate = entities.Where(a => a.Idx >= newIdx && a.Idx < oldIdx).Select(a => a);
                foreach (var toUpdateEntity in toUpdate)
                {
                    toUpdateEntity.Idx = toUpdateEntity.Idx + 1;
                }
            }
        }

        Detach(oldItem);
        Attach(item);   // re-attach to enable saving
    }

    Entry(item).State = EntityState.Modified;

    Commit();
}

我想知道的是这一行:

var entities = FindAll<T>().ToList().Cast<ISortable>();

我必须将LINQ to SQL表达式转换为列表才能将实体强制转换为ISortable。我需要将其转换为ISortable才能执行以下操作:

var toUpdate = entities.Where(a => a.Idx <= newIdx && a.Idx > oldIdx).Select(a => a);

接口公开了Idx属性。

问题是在FindAll()上调用ToList()会将整个表加载到内存中。

有没有办法在不首先加载整个表的情况下执行Where,而不会丢失通用实现?

这里的想法是我想对所有“可排序”的实体执行更新的一些常见操作。为了使这个工作,更新方法需要是通用的,以便处理各种类,但后来我需要接口来公开必要的字段......如果有更好的方法这样做(可能有),请让我知道。 : - )

3 个答案:

答案 0 :(得分:0)

  

问题是在FindAll()上调用ToList()会将整个表加载到内存中。

使用AsEnumerable代替ToList;它只是将编译时类型更改为IEnumerable<T>而不是IQueryable<T>,因此后续操作在内存中执行而不是在数据库中执行,但一次只处理一个项目(从中获取项目)根据后续操作的需要逐个DB。

答案 1 :(得分:0)

再次尝试,这次使用表达式。我认为这应该有效:

public void UpdateSingle<T>(T item) where T : class
{
    // If entity is Sortable, update the indexes of the records between the new and the old index of the updated entity
    var sortable = item as ISortable;
    if (sortable != null)
    {
        Detach(item);   // need to detach the entity from the context in order to retrieve the old values from DB
        var oldItem = Find<T>(sortable.Id);

        if (oldItem != null && sortable.Idx != oldItem.Idx)
        {
            UpdateSingleSortable(oldItem, sortable);
        }

        Detach(oldItem);
        Attach(item);   // re-attach to enable saving
    }

    Entry(item).State = EntityState.Modified;

    Commit();
}

public void UpdateSingleSortable<T>(T oldItem, ISortable sortable)
    where T : class
{
    var entities = FindAll<T>();

    var oldIdx = oldItem.Idx;
    var newIdx = sortable.Idx;

    if (newIdx > oldIdx)
    {
        var expression = GenerateExpressionA(oldItem, newIdx, oldIdx);
        var typedExpression = expression as Expression<Func<T, bool>>;
        var toUpdate = entities.Where(typedExpression).Select(a => a);
        foreach (var toUpdateEntity in toUpdate)
        {
            toUpdateEntity.Idx = toUpdateEntity.Idx - 1;
        }
    }
    else
    {
        var expression = GenerateExpressionB(oldItem, newIdx, oldIdx);
        var typedExpression = expression as Expression<Func<T, bool>>;
        var toUpdate = entities.Where(typedExpression).Select(a => a);
        foreach (var toUpdateEntity in toUpdate)
        {
            toUpdateEntity.Idx = toUpdateEntity.Idx + 1;
        }
    }
}

Expression GenerateExpressionB<T>(T t, int? newIdx, int? oldIdx)
{
    //    a => a.Idx >= newIdx && a.Idx < oldIdx
    var underlyingType = t.GetType();
    var idxGetter = underlyingType.GetProperty("Idx");

    Type genericFunc = typeof(Func<,>);
    Type[] typeArgs = { underlyingType, typeof(bool) };
    Type returnType = genericFunc.MakeGenericType(typeArgs);

    var param = Expression.Parameter(underlyingType);
    var toReturn = Expression.Lambda(
        returnType,
        Expression.And
        (
            Expression.GreaterThanOrEqual(
                Expression.MakeMemberAccess(param, idxGetter),
                Expression.Constant(newIdx, typeof(int?))
            ),
            Expression.LessThan(
                Expression.MakeMemberAccess(param, idxGetter),
                Expression.Constant(oldIdx, typeof(int?))
            )
        ),
        param);

    return toReturn;
}

Expression GenerateExpressionA<T>(T t, int? newIdx, int? oldIdx)
{
    //    a => a.Idx <= newIdx && a.Idx > oldIdx
    var underlyingType = t.GetType();
    var idxGetter = underlyingType.GetProperty("Idx");

    Type genericFunc = typeof(Func<,>);
    Type[] typeArgs = { underlyingType, typeof(bool) };
    Type returnType = genericFunc.MakeGenericType(typeArgs);

    var param = Expression.Parameter(underlyingType);
    var toReturn = Expression.Lambda(
        returnType,
        Expression.And
        (
            Expression.LessThanOrEqual(
                Expression.MakeMemberAccess(param, idxGetter),
                Expression.Constant(newIdx, typeof(int?))
            ),
            Expression.GreaterThan(
                Expression.MakeMemberAccess(param, idxGetter),
                Expression.Constant(oldIdx, typeof(int?))
            )
        ),
        param);
    toReturn.Dump();

    return toReturn;
}

答案 2 :(得分:0)

只需将方法的签名更改为以下内容:

public void UpdateSingle<T>(T item)
    where T : class, ISortable

然后,您不仅可以在数据库端执行查询(您不需要将集合拉入内存以使项目满足给定条件),您也不会在运行时进行检查;您将检查以确保T编译时实现ISortable