DataSheet View и Office 2010 x64

Если кто-либо имел счастье работать с SharePoint c помощью Office 2010 64-битной редакции, то обратил внимание что не работает DataSheet View при просмотре таблиц. Выпадает сообщение об отсутствии ActiveX компонент.

Проблема решаема: http://support.microsoft.com/kb/2266203



Unity 2.0

Давненько я не писал про Unity. За это время успела выйти новая версия этого IoC-контейнера и появилась новая версия Enterprise Library.

Первое заметное изменение в Unity: теперь требуется подключать одну сборку Microsoft.Practices.Unity. Хотя AOP для Unity все еще лежит в отдельной сборке Microsoft.Practices.Unit.Interception.

Интерфейс класса UnityContainer расширился методами IsRegistered и свойством Registrations. Последнее позволяет анализировать что мы уже положили в контейнер.

Три больших изменения в резолвинге. 

  1. Теперь можно резолвить Func<T>, который будет возвращать зарегистрированный в контейнере экземпляр T. Аналогично можно резолвить Func<IEnumerable<T>>, который будет возвращать все зарегистрированные в контейнере именованные зависимости типа T. Это дает возможность переконфигурировать контейнер во время работы программы, так чтобы остальной код об этом не знал.
  2. При наличии нескольких конструкторов в создаваемом классе по-умолчанию выбирается конструктор с максимальным количеством параметров.
  3. В метод Resolve теперь можно передавать объекты ResolverOverride, позволяющие переопределять поведение Unity при резолвинге.

Добавились два LifetimeManager. HierarchicalLifetimeManager – чтобы дочерние контейнеры резолвили свои экземпляры, а не брали родительский. Это может пригодиться в вебе при создании дочерних контейнеров на каждый запрос. PerResolveLifetimeManager – позволяет переиспользовать один экземпляр в пределах одного вызова метода Resolve.

Подробнее можно почитать в Change Log.

PS. Также есть версия для Silverlight.

PPS. Качать здесь.



Layered Architecture

Что такое “слоеная” архитектура многие себе представляют. Вкратце – все компоненты разделяем на некоторые логические слои, уменьшая связность между слоями. Но вот мало кто может сказать по какому принципу разделять слои и строить между ними взаимодействие.

У меня давно зрел принцип разделения на слои, который я не мог выразить словами. Прочитав вот этот пост я нашел правильную формулировку:

Никакие правки верхнего слоя никак не могут повлиять на нижний.

То есть если вам понадобилось в UI добавить новую форму или сделать в списке настраиваемую сортировку, то это никак не должно повлиять на слои логики и доступа к данным. ( вопрос в зал: ваши приложения соответствуют этому принципу?)



Группировка элементов в XSLT 1.0

По счастливой случайности я активно занимаюсь разработкой на SharePoint 2010. Для отображения данных в нем использует XSTL 1.0 в котором отсутствует оператор for-each-group. Вот чтобы сделать группировку надо написать такой код:

<xsl:stylesheet  version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:key name="имя-индекса" match="элементы-для-друппировки" use="ключ-для-группировки" />
    <xsl:template>
        <xsl:for-each 
            select="элементы-для-друппировки[count(. | key('имя-индекса', ключ-для-группировки)[1]) = 1]">
            <!--преобразование для группы-->
         <xsl:for-each select="key('имя-индекса', ключ-для-группировки)">
                <!--преобразование для элементов группы-->
         </xsl:for-each>
            <!--преобразование для группы-->
        </xsl:for-each>        
    </xsl:template>    
</xsl:stylesheet>


Сращивание expression tree

В предыдущем посте я показал как можно использовать обобщенное программирование при доступе к данным. С помощью пары простых методов можно заставить работать код вида

from e in context.Entity1Set.Visible()
select e;

Пример надуманный, так как предикат для определения видимости простой, и его несложно в каждом выражении записать явно. Но вместо него может быть предикат проверки прав доступа, сам по себе содержащий подзапросы, соединения и тому подобное.

Если написать что-то вроде

from e1 in context.Entity1Set.Visible()
from e2 in e1.Entity2.Visible()
select e2;

То такой код даже не скомпилируется, потому что e1.Entity2 не реализует интерфейс IQueryable<T>. Даже если бы такой ко компилировался, то наверняка отвалился бы в runtume, потому что Linq провайдер не знает что делать с методом Visible.

Проблему можно было бы решить если бы существовал простой способ поставить одно дерево выражений в другое. В языке F# есть возможность сращивания цитат (аналогов деревьев выражений для .NET), то есть подстановки одной цитаты в другую. Тогда вместо вызовов методов для коллекций можно было бы подставлять предикаты прямо в запрос. Что-то вроде такого:

from e1 in context.Entity1Set    
where Visible(e1)
from e2 in e1.Entity2
where Visible(e2)
select e2;

Но какой тип должен быть у предиката Visible? Чтобы код скомпилировался нужно чтобы Visible возвращал bool, а чтобы его можно было анализировать в runtime это должен быть тип Expression<Func<IVisible, bool>>

Идея в следующем, доработать FixupVisitor из предыдущего поста чтобы он находил в дереве выражений Expression<Func<…>> и выражение в само дерево. Чтобы такая подстановка компилировалась надо сделать метод, который будет преобразовывать типы, он же будет маркером, который скажет визитору, что надо подставить одно дерево в другое.

Нужен метод Splice (по англ. сращивание), который будет принимать Expression<Func<…,T>> и возвращать T.

public static T Splice<T,T1>
			(this Expression<Func<T1, T>> expr, T1 p1)
{
    throw new NotSupportedException();
}

Вызываться непосредственно этот метод не должен, только использоваться в expression tree.

Теперь добавим в FixupVisitor пару методов

protected override Expression VisitMethodCall
                   (MethodCallExpression node)
{
    if (!CheckSpliceMethod(node.Method))
    {
        return base.VisitMethodCall(node);
    }

    var args = node.Arguments;
    var expr = ExpressionExtensions.StripQuotes(args.First());

    //Если выражение не было подставлено непосредственно
    if (!(expr is LambdaExpression))
    {
        expr = (Expression)Expression.Lambda(expr)
                                     .Compile()
                                     .DynamicInvoke();
    }
    var lambda = expr as LambdaExpression;

    //Подстановка параметров в сращиваемое выражение
    return base.Visit(ExpressionExtensions.ReplaceAll
                        (lambda.Body, 
                         lambda.Parameters, 
                         args.Skip(1)));
}

//Проверяет что это нужный метод Splice
private bool CheckSpliceMethod(MethodInfo mi)
{
    if (mi.Name != "Splice" || mi.GetParameters().Length < 1)
        return false;
 
    var t = mi.GetParameters().First().ParameterType;

    return  t.IsGenericType &&
            t.GetGenericTypeDefinition() == typeof(Expression<>) &&
            t.GetGenericArguments()[0]
             .GetGenericArguments().Last() == mi.ReturnType;
}

Для замены формальных параметров лямбды на фактические параметры вызова метода Splice применяется  функция ReplaceAll, которая тоже реализована с помощью визитора.

class ExpressionReplacer : ExpressionVisitor
{
    Predicate<Expression> matcher;
    Func<Expression, Expression> replacer;

    public ExpressionReplacer
               (Predicate<Expression> matcher, 
                Func<Expression, Expression> replacer)
    {
        this.matcher = matcher;
        this.replacer = replacer;
    }

    public ExpressionReplacer(Expression searchFor, 
                              Expression replaceWith)
        : this(e => e == searchFor, _ => replaceWith)
    { }

    public override Expression Visit(Expression node)
    {            
        if(matcher(node))
        {
            return replacer(node);
        }

        return base.Visit(node);
    }
}

public static class ExpressionExtensions
{
    public static Expression StripQuotes(Expression expression)
    {
        if (expression.NodeType == ExpressionType.Quote)
        {
            return (expression as UnaryExpression).Operand;
        }
        else return expression;
    }

    public static Expression Replace
		      (Expression expression, 
                       Predicate<Expression> matcher, 
                       Func<Expression, Expression> replacer)
    {
        return new ExpressionReplacer(matcher, replacer)
                       .Visit(expression);
    }

    public static Expression Replace
                      (Expression expression, 
                       Expression searchFor, 
                       Expression replaceWith)
    {
        return new ExpressionReplacer(searchFor, replaceWith)
                       .Visit(expression);
    }

    public static Expression ReplaceAll
                  (Expression expression, 
                   IEnumerable<Expression> searchFor, 
                   IEnumerable<Expression> replaceWith)
    {
        return searchFor.Zip(replaceWith, Tuple.Create)
                        .Aggregate(expression, (e, p) => 
                             Replace(expression, 
                                     p.Item1, p.Item2));
    }
}

Теперь тестовый пример

static void Main(string[] args)
{
    Expression<Func<IVisible, bool>> 
	visiblePredicate = e => e.Visible;

    var context = new Model1Container();
    var q = from e1 in context.Entity1Set                    
            where visiblePredicate.Splice(e1)
            from e2 in e1.Entity2
            where visiblePredicate.Splice(e2)
            select e2;
    Console.WriteLine((q.Fix() as ObjectQuery).ToTraceString());
}

Дает результат

SELECT
[Extent1].[Id] AS [Id],
[Extent2].[Id] AS [Id1],
[Extent2].[Visible] AS [Visible],
[Extent2].[Entity1_Id] AS [Entity1_Id]
FROM  [dbo].[Entity1Set] AS [Extent1]
INNER JOIN [dbo].[Entity2Set] AS [Extent2] 
ON [Extent1].[Id] = [Extent2].[Entity1_Id]
WHERE ([Extent1].[Visible] = 1) 
    AND ([Extent2].[Visible] = 1)