0

我有几个实现 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 ?

这里的想法是我想对所有“可排序”的实体执行一些常见的更新操作。为此,更新方法需要是通用的才能处理各种类,但是我需要接口来公开必要的字段......如果有更好的方法(可能有),请告诉我. :-)

4

3 回答 3

0

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

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

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

于 2013-07-01T17:22:56.653 回答
0

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

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

于 2013-07-01T12:47:33.830 回答
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;
}
于 2013-07-01T12:55:37.710 回答