My extreme meta-programming workshop

Today, I was in Tel Aviv speaking about meta-programming for a workshop day.

As promised, I’m publishing the correction of the different exercises.

 

Exercise 1:

The goal of the first was to change the default Edmx T4 to generate

public partial class Customer
{
    private int _id;
    public int Id
    {
        get { return _id; }
        set
        {
            OnIdChanging(ref value);
            _id = value;
            OnIdChanged();
        }
    }
    partial void OnIdChanging(ref int value);
    partial void OnIdChanged();
    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            OnNameChanging(ref value);
            _name = value;
            OnNameChanged();
        }
    }
    partial void OnNameChanging(ref string value);
    partial void OnNameChanged();
}

instead of this:

public partial class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
}

For this, I replaced in the T4 file this code:

    var simpleProperties = typeMapper.GetSimpleProperties(entity);
    if (simpleProperties.Any())
    {
        foreach(var edmProperty in simpleProperties)
        {
#>
    <#=codeStringGenerator.Property(edmProperty)#>
<#
        }
}

by this one:

    var simpleProperties = typeMapper.GetSimpleProperties(entity);
    if (simpleProperties.Any())
    {
        PushIndent(CodeRegion.GetIndent(1));
        foreach (var edmProperty in simpleProperties)
            PropertyWithPartial(edmProperty, typeMapper, code);
        PopIndent();
    }

With the following PropertyWithPartial method:

public void PropertyWithPartial(EdmProperty edmProperty, TypeMapper typeMapper, CodeGenerationTools code)
{
    string fieldName = code.FieldName(edmProperty);
    string propertyName = code.Escape(edmProperty);
    string propertyTypeName = typeMapper.GetTypeName(edmProperty.TypeUsage);
#>
private <#=propertyTypeName#> <#=fieldName#>;
<#=Accessibility.ForProperty(edmProperty)#> <#=propertyTypeName#> <#=propertyName#>
{
    <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get { return <#=fieldName#>; }
    <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set
    {
        On<#=propertyName#>Changing(ref value);
        <#=fieldName#> = value;
        On<#=propertyName#>Changed();
    }
}
partial void On<#=propertyName#>Changing(ref <#=propertyTypeName#> value);
partial void On<#=propertyName#>Changed();

<#+
}

 

Exercise 2:

The goal of the second exercise was to analyze the methods of some extension method on a class (only methods and properties).

So from this code:

public class Employee
{
    public string LastName
    {
        get;
        set;
    }

    public string FirstName
    {
        get;
        set;
    }

    public void Foo()
    {
    }
}

public static class C
{
    public static string GetLastName(this Employee e)
    {
        return e.LastName;
    }

    public static void EmployeeFoo(this Employee e)
    {
        e.Foo();
    }

    public static string GetFirstNameAndFoo(this Employee e)
    {
        e.Foo();
        e.Foo();
        return string.Concat(e.LastName, e.FirstName);
    }
}

we wanted to generate (using T4) the following text file:

ClassLibrary1.C.EmployeeFoo(ClassLibrary1.Employee) dependent methods
ClassLibrary1.Employee.Foo()
ClassLibrary1.C.GetFirstNameAndFoo(ClassLibrary1.Employee) dependent methods
ClassLibrary1.Employee.Foo()
ClassLibrary1.C.GetLastName(ClassLibrary1.Employee) dependent properties
ClassLibrary1.Employee.LastName
ClassLibrary1.C.GetFirstNameAndFoo(ClassLibrary1.Employee) dependent properties
ClassLibrary1.Employee.LastName
ClassLibrary1.Employee.FirstName

So this is the T4 template I used:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".txt" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="Roslyn.Compilers"#>
<#@ assembly name="Roslyn.Compilers.CSharp"#>
<#@ assembly name="Roslyn.Services"#>
<#@ assembly name="Roslyn.Services.CSharp"#>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="Roslyn.Compilers.Common"#>
<#@ import namespace="Roslyn.Compilers.CSharp"#>
<#@ import namespace="Roslyn.Services"#>
<#+
void Analyze(string slnPath, string csProjPath, string filePath, string entityClass, string extensionClass)
{
    var solution = Solution.Load(Path.GetFullPath(Host.ResolvePath(slnPath)));
    var project = solution.Projects.FirstOrDefault(p => p.FilePath == Path.GetFullPath(Host.ResolvePath(csProjPath)));
    var document = project.Documents.First(d => d.FilePath == Path.GetFullPath(Host.ResolvePath(filePath)));
    var documentTree = document.GetSyntaxTree();
    var compilation = project.GetCompilation();
    var compilationUnit = (CompilationUnitSyntax)documentTree.GetRoot();
    var semanticModel = compilation.GetSemanticModel(documentTree);
    var sourceVisitor = new SourceVisitor(semanticModel, entityClass, extensionClass);
    sourceVisitor.Visit(compilationUnit);
    foreach(var m in sourceVisitor.DependenceMethods)
    {
#>
<#=m.Key#> dependent methods
<#+
        foreach (var dm in m.Value.Distinct())
        {
#>
    <#=dm#>
<#+
        }
    }
    foreach(var m in sourceVisitor.DependenceProperties)
    {
#>
<#=m.Key#> dependent properties
<#+
        foreach (var dp in m.Value.Distinct())
        {
#>
    <#=dp#>
<#+
        }
    }
}

class SourceVisitor : SyntaxVisitor
{
    private ISemanticModel _semanticModel;
    private string _entityClass;
    private string _extensionClass;
    private bool _stop;
    private MethodSymbol _method;

    public SourceVisitor(ISemanticModel semanticModel, string entityClass, string extensionClass)
    {
        _semanticModel = semanticModel;
        _entityClass = entityClass;
        _extensionClass = extensionClass;
        DependenceMethods = new Dictionary<MethodSymbol, List<MethodSymbol>>();
        DependenceProperties = new Dictionary<MethodSymbol, List<PropertySymbol>>();
    }

    public override void Visit(SyntaxNode node)
    {
        base.Visit(node);
        if (! _stop)
            foreach (var childNode in node.ChildNodes())
                Visit(childNode);
    }

    public override void VisitClassDeclaration(ClassDeclarationSyntax node)
    {
_stop = node.Identifier.ValueText != _extensionClass;
    }

    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        _method = (MethodSymbol)_semanticModel.GetDeclaredSymbol(node);
    }

    public override void VisitInvocationExpression ( InvocationExpressionSyntax node )
    {
        var methodSymbol = (MethodSymbol)_semanticModel.GetSymbolInfo(node).Symbol;
        if (methodSymbol.ContainingType.Name == _entityClass)
        {
            List<MethodSymbol> methods;
            if (! DependenceMethods.TryGetValue(_method, out methods))
                DependenceMethods.Add(_method, methods = new List<MethodSymbol>());
            methods.Add(methodSymbol);
        }
    }

    public override  void VisitMemberAccessExpression ( MemberAccessExpressionSyntax node )
    {
        var propertySymbol = _semanticModel.GetSymbolInfo(node).Symbol as PropertySymbol;
        if (propertySymbol != null && propertySymbol.ContainingType.Name == _entityClass)
        {
            List<PropertySymbol> properties;
            if (! DependenceProperties.TryGetValue(_method, out properties))
                DependenceProperties.Add(_method, properties = new List<PropertySymbol>());
            properties.Add(propertySymbol);
        }
    }

    public Dictionary<MethodSymbol, List<MethodSymbol>> DependenceMethods { get; private set; }
    public Dictionary<MethodSymbol, List<PropertySymbol>> DependenceProperties { get; private set; }
}
#>

Then you just have to call the method Analyze in your T4.

 

Exercise 3:

The goal of the third was to generate a new class that includes validation based on metadata from some code metadata.

From this code:

public static class MetadataDefinition
{
    public static void DefineMaxLength<T>(Func<T, string> getProperty, int maxLength)
    {
    }
    public static void DefineMaxLength<T>(Func<T, string> getProperty, Func<T, int?> getMaxLength)
    {
    }
    public static void DefinePattern<T>(Func<T, string> getProperty, string pattern)
    {
    }
    public static void DefinePattern<T>(Func<T, string> getProperty, Func<T, string> getPattern)
    {
    }
}

public class Address
{
    public string AddressLine { get; set; }
    public string PostalCode { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
}

public static class Specifications
{
    public static void DefineMetadata()
    {
        MetadataDefinition.DefineMaxLength<Address>(a => a.City, 100);
        MetadataDefinition.DefineMaxLength<Address>(a => a.PostalCode,
            a => a.Country != null && a.Country.ToUpper() == "FRANCE" ? 5 : 15);
        MetadataDefinition.DefinePattern<Address>(a => a.PostalCode,
            a => a.Country != null && a.Country.ToUpper() == "FRANCE" ? @"^(\d{2}|(2(A|B)))\d{3}$" : null);
    }
}

we wanted to generate this one:

public class AddressWithValidation
{
    public string AddressLine
    {
        get;
        set;
    }

    private string _PostalCode;
    public string PostalCode
    {
        get
        {
            return _PostalCode;
        }
        set
        {
            ValidatePostalCodeMaxLength(value);
            ValidatePostalCodePattern(value);
            _PostalCode = value;
        }
    }

    private string _City;
    public string City
    {
        get
        {
            return _City;
        }

        set
        {
            ValidateCityMaxLength(value);
            _City = value;
        }
    }

    private string _Country;
    public string Country
    {
        get
        {
            return _Country;
        }

        set
        {
            _Country = value;
            ValidatePostalCodeMaxLength(PostalCode);
            ValidatePostalCodePattern(PostalCode);
        }
    }

    private void ValidatePostalCodeMaxLength(string value)
    {
        if (value == null)
            return;
        int ? maxLength = this.Country != null && this.Country.ToUpper() == "FRANCE" ? 5 : 15;
        if (maxLength.HasValue && value.Length > maxLength)
            throw new InvalidOperationException();
    }

    private void ValidatePostalCodePattern(string value)
    {
        if (value == null)
            return;
        string pattern = this.Country != null && this.Country.ToUpper() == "FRANCE" ? @"^(\d{2}|(2(A|B)))\d{3}$" : null;
        if (pattern != null && !Regex.IsMatch(value, pattern))
            throw new InvalidOperationException();
    }

    private void ValidateCityMaxLength(string value)
    {
        if (value == null)
            return;
        if (value.Length > 100)
            throw new InvalidOperationException();
    }
}

For this, I used the following T4 file:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="Roslyn.Compilers"#>
<#@ assembly name="Roslyn.Compilers.CSharp"#>
<#@ assembly name="Roslyn.Services"#>
<#@ assembly name="Roslyn.Services.CSharp"#>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="Roslyn.Compilers.Common"#>
<#@ import namespace="Roslyn.Compilers.CSharp"#>
<#@ import namespace="Roslyn.Services"#>
<#+
void WriteValidations(string slnPath, string csProjPath, string filePath, string entityClassName,
string specificationsClassName)
{
    var solution = Solution.Load(Path.GetFullPath(Host.ResolvePath(slnPath)));
    var project = solution.Projects.FirstOrDefault(p => p.FilePath == Path.GetFullPath(Host.ResolvePath(csProjPath)));
    var document = project.Documents.First(d => d.FilePath == Path.GetFullPath(Host.ResolvePath(filePath)));
    var documentTree = document.GetSyntaxTree();
    var compilation = project.GetCompilation();
    var compilationUnit = (CompilationUnitSyntax)documentTree.GetRoot();
    var semanticModel = compilation.GetSemanticModel(documentTree);
    var validationMetadataVisitor = new ValidationMetadataVisitor(semanticModel, entityClassName, specificationsClassName);
    validationMetadataVisitor.Visit(compilationUnit);
#>
<#=new ValidationMetadataRewriter(entityClassName, validationMetadataVisitor.ValidationMetadata, semanticModel)
.Visit(compilationUnit).NormalizeWhitespace().ToString()#>
<#+
}


class ValidationMetadataVisitor : SyntaxVisitor
{
    private ISemanticModel _semanticModel;
    private string _entityClassName;
    private string _specificationClassName;
    private bool _stop;
    private List<string> _dependentProperties;

    public override void Visit(SyntaxNode node)
    {
        base.Visit(node);
        if (! _stop)
            foreach (var childNode in node.ChildNodes())
                Visit(childNode);
    }

    public ValidationMetadataVisitor(ISemanticModel semanticModel, string entityClassName, string specificationClassName)
    {
        _semanticModel = semanticModel;
        _entityClassName = entityClassName;
        _specificationClassName = specificationClassName;
        ValidationMetadata = new List<ValidationMetadata>();
    }

    public List<ValidationMetadata> ValidationMetadata { get; private set; }

    public override void VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        _stop = node.Identifier.ValueText != _specificationClassName;
        base.VisitClassDeclaration(node);
    }

    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        foreach (var statement in node.Body.Statements)
        {
         var invocation = (InvocationExpressionSyntax)statement.ChildNodes().Single();
        
var invocationMethod = (MethodSymbol)_semanticModel.GetSymbolInfo(invocation).Symbol;
_dependentProperties = new List<string>();
var type = invocationMethod.TypeArguments.Single();
if (type.Name == _entityClassName)
{
Visit(statement);
                var property = ((MemberAccessExpressionSyntax)((SimpleLambdaExpressionSyntax)invocation.ArgumentList.
Arguments[0].Expression).Body).Name.Identifier.ValueText;
                ValidationMetadata.Add(new ValidationMetadata {
MethodName = invocationMethod.Name,
Property = property,
DependentProperties = _dependentProperties.Where(dp => dp != property).ToList(),
ConstantExpression = invocation.ArgumentList.Arguments[1].Expression
as LiteralExpressionSyntax,
LambdaExpression = invocation.ArgumentList.Arguments[1].Expression
as SimpleLambdaExpressionSyntax });
            }
        }
    }


    public override void VisitMemberAccessExpression ( MemberAccessExpressionSyntax node )
    {
        var propertySymbol = _semanticModel.GetSymbolInfo(node).Symbol as PropertySymbol;
        if (propertySymbol != null && propertySymbol.ContainingType.Name == _entityClassName)
            _dependentProperties.Add(propertySymbol.Name);
    }
}

public
 class ValidationMetadata
{
    public string MethodName { get; set; }
    public string Property { get; set; }
    public IEnumerable<string> DependentProperties { get; set; }
    public SimpleLambdaExpressionSyntax LambdaExpression { get; set; }
    public LiteralExpressionSyntax ConstantExpression { get; set; }
}

public class ValidationMetadataRewriter : SyntaxRewriter
{
    private string _entityClassName;
    private List<ValidationMetadata> _validationMetadata;
    private ISemanticModel _semanticModel;

    public ValidationMetadataRewriter(string entityClassName, List<ValidationMetadata> validationMetadata,
ISemanticModel semanticModel)
    {
        _entityClassName = entityClassName;
        _validationMetadata = validationMetadata;
        _semanticModel = semanticModel;
    }

    public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        if (node.Identifier.ValueText != _entityClassName)
            return null;

        var members = node.Members.ToList();
        foreach (var m in node.Members)
        {
            var prop = m as PropertyDeclarationSyntax;
            if (prop != null)
            {
                var properties = _validationMetadata.Where(p => p.Property == prop.Identifier.ValueText).ToList();
                var dependentProperties = _validationMetadata.Where(p => p.DependentProperties.Any(
dp => dp == prop.Identifier.ValueText)).ToList();
                if (properties.Count != 0 || dependentProperties.Count != 0)
                {
                    int index = members.IndexOf(prop);
                    members.RemoveAt(index);
                    members.Insert(index, Syntax.FieldDeclaration(
                        Syntax.VariableDeclaration(prop.Type)
                            .AddVariables(
                                Syntax.VariableDeclarator("_" + prop.Identifier.ValueText)))
                        .WithModifiers(
                            Syntax.TokenList(
                                Syntax.Token(SyntaxKind.PrivateKeyword))));
                    members.Insert(index + 1,
                        Syntax.PropertyDeclaration(prop.Type, prop.Identifier.ValueText)
                            .WithModifiers(prop.Modifiers)
                            .WithAccessorList(
                                Syntax.AccessorList(
                                    Syntax.List<AccessorDeclarationSyntax>(
                                        Syntax.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration,
                                            Syntax.Block(
                                                Syntax.ParseStatement(string.Concat("return _",
prop.Identifier.ValueText,
";")))),
                                        Syntax.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration,
                                            Syntax.Block(
                                                properties.Select(p => Syntax.ParseStatement(string.Concat("Validate",
p.Property, p.MethodName.Substring(6),
"(value);")))
                                                .Union(new [] { Syntax.ParseStatement(string.Concat("_",
prop.Identifier.ValueText,
" = value;")) })
                                                .Union(dependentProperties.Select(dp => Syntax.ParseStatement(
string.Concat("Validate", dp.Property, dp.MethodName.Substring(6),
"(", dp.Property, ");"))))))))));
                    foreach (var p in properties)
                    {
                        var methodBodyStatements = new List<StatementSyntax>();
                        methodBodyStatements.Add(Syntax.ParseStatement("if (value == null) return;"));
                        if (p.LambdaExpression != null)
                            methodBodyStatements.Add(Syntax.LocalDeclarationStatement(
                                Syntax.VariableDeclaration(
                                    Syntax.ParseTypeName(((MethodSymbol)_semanticModel.GetSymbolInfo(p.LambdaExpression)
.Symbol).ReturnType.ToString()))
                                    .WithVariables(
                                        Syntax.SeparatedList<VariableDeclaratorSyntax>(
                                            Syntax.VariableDeclarator(p.MethodName.Substring(6, 1).ToLower() +
p.MethodName.Substring(7))
                                                .WithInitializer(
                                                    Syntax.EqualsValueClause(
                                                        (ExpressionSyntax)new ReplaceParameterByThisRewriter(
p.LambdaExpression.Parameter.Identifier.ValueText)
.Visit(p.LambdaExpression.Body)))))));
                        switch (p.MethodName)
                        {
                            case "DefineMaxLength":
                                methodBodyStatements.Add(Syntax.ParseStatement((p.LambdaExpression == null ?
string.Concat("if (value.Length > ", p.ConstantExpression.NormalizeWhitespace()
.ToString(),
")") : "if (maxLength.HasValue && value.Length > maxLength)") +
"throw new InvalidOperationException();"));
                                break;
                            case "DefinePattern":
                                methodBodyStatements.Add(Syntax.ParseStatement(string.Concat("if (",
p.LambdaExpression ==
null ? "" : "pattern != null && ", "! Regex.IsMatch(value, ",
p.LambdaExpression ==
null ? p.ConstantExpression.NormalizeWhitespace().ToString()
:
"pattern", "))throw new InvalidOperationException();")));
                                break;
                        }
                        members.Add(
                            Syntax.MethodDeclaration(Syntax.ParseTypeName("void"), string.Concat("Validate",
p.Property, p.MethodName.Substring(6)))
                                .WithModifiers(
                                    Syntax.TokenList(
                                        Syntax.Token(SyntaxKind.PrivateKeyword)))
                                .WithParameterList(
                                    Syntax.ParameterList(
                                        Syntax.SeparatedList<ParameterSyntax>(
                                            Syntax.Parameter(
                                                Syntax.Identifier("value"))
                                                .WithType(prop.Type))))
                                .WithBody(
                                    Syntax.Block(methodBodyStatements)));
                    }
                }
            }
        }
        return node.WithIdentifier(Syntax.Identifier(node.Identifier.ValueText + "WithValidation"))
.WithMembers(Syntax.List<MemberDeclarationSyntax>(members));
    }
}

 
public class ReplaceParameterByThisRewriter : SyntaxRewriter
{
    private string _parameterName;

    public ReplaceParameterByThisRewriter(string parameterName)
    {
        _parameterName = parameterName;
    }

    public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
    {
        if (node.Identifier.ValueText == _parameterName)
            return Syntax.ThisExpression ();
        return base.VisitIdentifierName(node);
    }
}
#>

 

Exercise 4:

The goal of the fourth exercise was to update the current document classes in order to add a foo parameter on constructors (or to create a constructor is the class does not have) using NuGet.

For this, I created a ConsoleApplication with the following code:

using Roslyn.Compilers.CSharp;
using System.Collections.Generic;
using System.IO;
using System.Linq;


namespace SELA.NuGet
{
    class Program
    {
        static void Main(string[] args)
        {
            var filePath = args[0];
            string fileCode;
            using (var sr = new StreamReader(filePath))
            {
                fileCode = sr.ReadToEnd();
            }
            var fileSyntax = Syntax.ParseCompilationUnit(fileCode);
            fileCode = new ConstructorAddFooParamater().Visit(fileSyntax).NormalizeWhitespace().ToString();
            using (var sw = new StreamWriter(filePath))
            {
                sw.Write(fileCode);
            }
        }
    }

    class ConstructorAddFooParamater : SyntaxRewriter
    {
        public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
        {
            node = (ClassDeclarationSyntax)base.VisitClassDeclaration(node);
            var additionalMembers = new List<MemberDeclarationSyntax>();
            additionalMembers.Add(Syntax.FieldDeclaration(Syntax.VariableDeclaration(Syntax.ParseTypeName("string"))
.WithVariables(
Syntax.SeparatedList(Syntax.VariableDeclarator("_foo"))))
.WithModifiers(
Syntax.TokenList(Syntax.Token(SyntaxKind.PrivateKeyword))));
            if (!node.Members.OfType<ConstructorDeclarationSyntax>().Any())
                additionalMembers.Add((ConstructorDeclarationSyntax)VisitConstructorDeclaration(
Syntax.ConstructorDeclaration(node.Identifier.ValueText)
.WithModifiers(
Syntax.TokenList(Syntax.Token(SyntaxKind.PublicKeyword)))));
            return node.WithMembers(Syntax.List<MemberDeclarationSyntax>(additionalMembers.Union(node.Members)));
        }

        public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
        {
            var parameters = node.ParameterList.Parameters.ToList();
            parameters.Add(Syntax.Parameter(Syntax.Identifier("foo")).WithType(Syntax.ParseTypeName("string")));
            return node.WithParameterList(Syntax.ParameterList(Syntax.SeparatedList(parameters, parameters.Skip(1)
.Select(p =>
Syntax.Token(SyntaxKind.CommaToken))))).WithBody(Syntax.Block((node.Body == null ?
Enumerable.Empty<StatementSyntax>() : node.Body.Statements).Union(new[] {
Syntax.ParseStatement("_foo = foo;") })));
        }
    }
}

Then, I compiled my application.

Then, using NuGetPackageExplorer, I created a new NuGet, then I add the Tools folder. In it I add the following init.ps1 file:

param($installPath, $toolsPath, $package)

Import-Module (Join-Path $toolsPath Sela.psm1)

 

Then I add a new file Sela.psm1 into the Tools folder:

function GetToolsPath()
{
    return [System.IO.Path]::GetDirectoryName((Get-Module Sela | select -property path)[-1].Path)
}
function SelaSample()
{
    $exePath = Join-Path (GetToolsPath) SELA.NuGet.exe
    $exeArgs = @('"' + $DTE.ActiveDocument.FullName + '"')
    start-process -filepath $exePath -ArgumentList $exeArgs -Wait
}
Export-ModuleMember SelaSample

 

And I also copy the SELA.NuGet.exe into the Tools folder.

After doing it and installing the NuGet package, I can use the SelaSample command on the Package Manager Console in VS to change the current file as expected.

Posted by Matthieu MEZIL | with no comments

WCF Data Services vs WAQS – Performance aspect

The main reason why I made WAQS was the query limitations with WCF Data Services and WCF RIA Services.

WAQS changed a lot from June and it has a lot of features in addition of querying from the client on 3 tiers applications.

BTW, ping me if you want to learn more about it.

In previous days, I optimized WAQS. So I wanted to test it vs WCF Data Services to see if WAQS features gain does not have a bad impact on performances.

So I test several simples queries on Contoso database which has more data than Northwind.

Note that, in my test, I use default configuration of WAQS and of WCF Data Services.

 

This is the result for WCF Data Services:

  • Get 18,869 customers: 6,693 ms
    var query = _context.DimCustomers;
    var customers = (await Task.Factory.FromAsync(query.BeginExecute(null, null),
        ar => query.EndExecute(ar))).ToList();
  • Get customers CompanyName: 2,652 ms
    var query = (DataServiceQuery<DimCustomer>)_context.DimCustomers
        .Select(c => new DimCustomer { CustomerKey = c.CustomerKey, CompanyName = c.CompanyName });
    var customers = (await Task.Factory.FromAsync(query.BeginExecute(null, null),
        ar => query.EndExecute(ar)))
        .Select(c => c.CompanyName).ToList();
  • Get 100 customers with their online sales: 9,672 ms
    var query = (DataServiceQuery<DimCustomer>)_context.DimCustomers
        .Expand("FactOnlineSales")
        .Take(100);
    var customers = (await Task.Factory.FromAsync(query.BeginExecute(null, null),
        ar => query.EndExecute(ar))).ToList();
  • Get 100 customers with their online sales with products of this ones: 27,021 ms
    var query = (DataServiceQuery<DimCustomer>)_context.DimCustomers
        .Expand("FactOnlineSales/DimProduct")
        .Take(100);
    var customers = (await Task.Factory.FromAsync(query.BeginExecute(null, null),
        ar => query.EndExecute(ar))).ToList();
  • Get 100 customers with their online sales with products and categories of this ones: 42,604 ms
    var query = (DataServiceQuery<DimCustomer>)_context.DimCustomers
        .Expand("FactOnlineSales/DimProduct/DimProductSubcategory/DimProductCategory")
        .Take(100);
    var customers = (await Task.Factory.FromAsync(query.BeginExecute(null, null),
        ar => query.EndExecute(ar))).ToList();
  • Get 200 customers with their online sales with products and categories of this ones: Exception !
    var query = (DataServiceQuery<DimCustomer>)_context.DimCustomers
        .Expand("FactOnlineSales/DimProduct/DimProductSubcategory/DimProductCategory")
        .Take(200);
    var customers = (await Task.Factory.FromAsync(query.BeginExecute(null, null),
        ar => query.EndExecute(ar))).ToList();

Now, the same with WAQS:

  • Get 18,869 customers: 3,307 ms (instead of 6,693 ms)
    var customers = (await _context.DimCustomers.AsAsyncQueryable().Execute()).ToList();
  • Get customers CompanyName: 187 ms (instead of 2,652 ms)
    var customers = (await _context.DimCustomers.AsAsyncQueryable()
        .Select(c => c.CompanyName)
        .Execute()).ToList();
  • Get 100 customers with their online sales: 3,260 ms (au lieu de 9,672 ms)
    var customers = (await _context.DimCustomers.AsAsyncQueryable()
        .OrderBy(c => c.CustomerKey)
        .Take(100)
        .IncludeFactOnlineSales()
        .Execute()).ToList();
  • Get 100 customers with their online sales with products of this ones: 4,336 ms (instead of 27,021 ms)

    The code is blocked by msmvps. It’s available on my French blog http://blogs.codes-sources.com/matthieu/archive/2013/02/11/wcf-data-services-vs-waqs.aspx. Sorry for that.
     
  • Get 100 customers with their online sales with products and categories of this ones: 4,883 ms (instead of 42,604 ms)

    The code is blocked by msmvps. It’s available on my French blog http://blogs.codes-sources.com/matthieu/archive/2013/02/11/wcf-data-services-vs-waqs.aspx. Sorry for that.
     
  • Get 200 customers with their online sales with products and categories of this ones: 7,029 ms (instead of exception)

    The code is blocked by msmvps. It’s available on my French blog http://blogs.codes-sources.com/matthieu/archive/2013/02/11/wcf-data-services-vs-waqs.aspx. Sorry for that.
     


If we do not take in consideration the infinite rate of the last query, WAQS is between 2.02 and 14.18 faster than WCF Data Services with an average of 6,83 in my tests.

That's a load off my mind! :)

// Note that the current version of WAQS is not public yet

FileSystemWatcher, Rx and Throttle

Imagine that we want to be notified if a directory files changed.

In general, when we use files, we don’t need to have a real time application.

Moreover, if we change many files in same time, we perhaps don’t need to have many notification and one notification at the end could be enough.

 

For this case, Rx and particularly Throttle method is very useful.

In our case, I create a FileChangedEvent which can contain notification information we may need.

public class FileChangedEvent

{

}

 

Then, I create a class FileWatcher that returns an IObservable<FileChangedEvent>.

This class need the directory path, the type of files to watch and the minimum time between two changes before having a notifications.

public class FileWatcher

{

    public FileWatcher(string path, string filter, TimeSpan throttle)

    {

        Path = path;

        Filter = filter;

        Throttle = throttle;

    }

 

    public string Path { get; private set; }

    public string Filter { get; private set; }

    public TimeSpan Throttle { get; private set; }

 

    public IObservable<FileChangedEvent> GetObservable()

    {

        return Observable.Create<FileChangedEvent>(observer =>

            {

                FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Path, Filter) { EnableRaisingEvents = true };

                FileSystemEventHandler created = (_, __) => observer.OnNext(new FileChangedEvent());

                FileSystemEventHandler changed = (_, __) => observer.OnNext(new FileChangedEvent());

                RenamedEventHandler renamed = (_, __) => observer.OnNext(new FileChangedEvent());

                FileSystemEventHandler deleted = (_, __) => observer.OnNext(new FileChangedEvent());

                ErrorEventHandler error = (_, errorArg) => observer.OnError(errorArg.GetException());

                fileSystemWatcher.Created += created;

                fileSystemWatcher.Changed += changed;

                fileSystemWatcher.Renamed += renamed;

                fileSystemWatcher.Deleted += deleted;

                fileSystemWatcher.Error += error;

                return () =>

                    {

                        fileSystemWatcher.Created -= created;

                        fileSystemWatcher.Changed -= changed;

                        fileSystemWatcher.Renamed -= renamed;

                        fileSystemWatcher.Deleted -= deleted;

                        fileSystemWatcher.Error -= error;

                        fileSystemWatcher.Dispose();

                    };

            }).Throttle(Throttle);

    }

}

 

Now we can use the observable like this:

var fileWatcher = new FileWatcher(".", "*.*", TimeSpan.FromSeconds(5));
var fileObserver = fileWatcher.GetObservable().Subscribe(fce => { /*TODO*/ });

Imagine that if we have n changes on the directory in less than 5 seconds between two consecutive changes.
With this code, we have one only notification, 5 seconds after the last one.

Posted by Matthieu MEZIL | 1 comment(s)
Filed under: ,

WCF Async Queryable Services features

I already made two videos on WCF Async Queryable Services architecture and tooling.

I just published a new one to present WAQS features.

WCF Async Queryable Services Features

Now you can enjoy with WAQS!

Give me your feedbacks please

Using WAQS tooling

Using WAQS tooling is not something very intuitive. So I made a video to explain you how to do.

Now you can enjoy with WAQS!

WAQSProp snippet

In my video on WCF Async Queryable Services tooling, I talked about a snippet. You can download it here.

Roslyn fluent APIs: RoslynHelper NuGet package

If you use Roslyn and you want to simplify the code rewriter, I recommend you to install my NuGet package RoslynHelper.

Posted by Matthieu MEZIL | with no comments
Filed under: , ,

Use Roslyn to improve C#/VB compiler

I published a POC using Roslyn to improve C# compiler.

Enjoy! :)

Posted by Matthieu MEZIL | with no comments
Filed under: , ,

WCF Async Queryable Services: the architecture

If you follow me on twitter, you probably know that I’m working on a project named WCF Async Queryable Services.

I made my first video on WCF Async Queryable Services to explain my architecture.

WCF Async Queryable Services - Architecture

Thanks for your feedbacks

LINQ Expressions are more permissive than C#

When we use Collection initializers, we can’t write many instructions in the initialization:

public class C
{
    public int MyProperty { get; set; }
}


var test = new List<C>() { new C { MyProperty = { int i = 1; i += 1 ; return i; } } };

This code does not compile.

 

But, using LINQ Expressions, it does!

var v = Expression.Variable(typeof(int));
var test = Expression.Lambda<Func<List<C>>>(
    Expression.ListInit(
        Expression.New(typeof(List<C>)), 
        Expression.ElementInit(
            typeof(List<C>).GetMethod("Add"), 
            Expression.MemberInit(
                Expression.New(typeof(C)), 
                Expression.Bind(typeof(C).GetProperty("MyProperty"), 
                Expression.Block(
                    new ParameterExpression[] { v }, 
                    Expression.Assign(v, Expression.Constant(1)), 
                    Expression.AddAssign(v, Expression.Constant(1)), 
                    v)))))).Compile()();


Posted by Matthieu MEZIL | 4 comment(s)
Filed under: ,

EF: why Include method is an anti-pattern IMHO? Part 5: many to many relationships

I recently blogged to explain why I found that Include method is an anti-pattern IMHO.

EF: Why Include method is an anti-pattern IMHO?

EF: Why Include method is an anti-pattern IMHO even with many to one navigation properties? 2/3

EF: Why Include method is an anti-pattern IMHO? 3/3

EF- Why Include method is an anti-pattern IMHO- Conclusion

But I didn’t show how to do it with many to many relationships.

I will do on this post.

This is my model:

image

I have 1000 products, 100 categories and 4497 associations in my DB.

I use a local DB what is better for EF Include. // Don’t forget it, it would be worse for EF Include method with SQL Azure or any far DB… Moreover, the bigger entities are, the worse it is for the EF Include. In my case, entities are really very very short

The ā€œofficialā€ way to get the 100 first Products with their Categories is the following:

var products = context.Products.Include("Categories").Take(100).ToList();

 

Now my way is the following:

object categoriesLock = new object();
object productsCategoriesLock = new object();
Task productsCategoriesTask = new Task(() =>
    {
        using (var productsCategoriesContext = new Many2ManyIncludeEntities())
        {
            var productsCategoriesIds = productsCategoriesContext.Products.Take(100).SelectMany(p => p.Categories.Select(c => new { p.ProductId, c.CategoryId })).ToList();
            lock (productsCategoriesLock)
            {
                var loadedProducts = context.ObjectStateManager.GetObjectStateEntries(EntityState.Unchanged).Select(ose => ose.Entity).OfType<Product>().ToList();
                var loadedCategories = context.ObjectStateManager.GetObjectStateEntries(EntityState.Unchanged).Select(ose => ose.Entity).OfType<Category>().ToList();
                foreach (var pc in productsCategoriesIds)
                {
                    var product = loadedProducts.FirstOrDefault(p => p.ProductId == pc.ProductId);
                    var category = loadedCategories.FirstOrDefault(c => c.CategoryId == pc.CategoryId);
                    if (product != null && category != null)
                        product.Categories.Attach(category);
                }
            }
        }
    });
Task categoriesTask = new Task(() =>
    {
        lock (productsCategoriesLock)
        {
            productsCategoriesTask.Start();
            using (var categoriesContext = new Many2ManyIncludeEntities())
            {
                categoriesContext.Categories.MergeOption = MergeOption.NoTracking;
                var categories = categoriesContext.Categories.Where(c => c.Products.Any(p => categoriesContext.Products.Take(100).Contains(p))).ToList();
                lock (categoriesLock)
                {
                    foreach (var c in categories)
                        context.Categories.Attach(c);
                }
            }
        }
    });
lock (categoriesLock)
{
    categoriesTask.Start();
    var products = context.Products.Take(100).ToList();
}
Task.WaitAll(categoriesTask, productsCategoriesTask);

So my code is really more complex but what about performance?

 

Even with best condition for EF way (local DB, very short entities), my way is really better: for the first execution, EF Include executes it on 835 ms when mine is 130 (6.42 faster). For the second an other ones, EF Include executes it on 342 ms vs 72 for mine (4.75 faster).

Now you have the choice between performance vs simplicity.

TechDays Paris Roslyn compiler demo

Two days ago, I spoke at Microsoft TechDays in Paris, France on Roslyn.

I made several demos including one where I build my own C# compiler that allows me to add features on C#.

 

This compiler supports AOP in order to implement INotifyPropertyChanged.

So with this code:

[NotifyPropertyChanged]

public class TestNotifyPropertyChanged

{

    public int P1 { get; set; }

 

    private string _p2;

    public string P2

    {

        get { return _p2; }

        set 

        {

            if (_p2 != null)

                _p2 = value;

        }

    }

}

the compilation works like if you wrote this one:

public class TestNotifyPropertyChanged : System.ComponentModel.INotifyPropertyChanged
{
    public int P1
    {
        get
        {
            return _p1;
        }
 
        set
        {
            _p1 = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("P1"));
        }
    }
 
    private string _p2;
    public string P2
    {
        get
        {
            return _p2;
        }
 
        set
        {
            if (_p2 != null)
                _p2 = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("P2"));
        }
    }
 
    private int _p1;
    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
}

 

My second C# improvement was to be able to combine return and yield return on the same method and to introduce yield return many keyword.

In this case, this code:

public IEnumerable<int> GetInts(bool b)
{
    if (b)
        return Enumerable.Range(1, 2);
    yield return many Enumerable.Range(1, 2);
    yield return 4;
}

will be translated (still on compilation process) by the following:

public IEnumerable<int> GetInts(bool b)
{
    if (b)
    {
        foreach (int yieldReturnVarLoop in Enumerable.Range(1, 2))
            yield return yieldReturnVarLoop;
        yield break;
    }
 
    foreach (int yieldReturnVarLoop in Enumerable.Range(1, 2))
        yield return yieldReturnVarLoop;
    yield return 4;
}

 

The last one was to support generic constraint constructor with parameters.

With my own compiler, I can use the following code:

public class C<T>
    where T : new(string, bool)
{
    public T CreateT(string s, bool b)
    {
        return new T(s, b);
    }
}
 
public class Test
{
    public static string Foo()
    {
        return new C<C2>().CreateT("A", true).S; 
    }
}

which will be translated by this one:

public class Test
{
    public static string Foo()
    {
        return new C<C2>() { FactoryT = (aT0, aT1) => new ConsoleApplication1.C2(aT0, aT1) }.CreateT("A", true).S;
    }
}
 
public class C<T>
{
    public T CreateT(string s, bool b)
    {
        return FactoryT(s, b);
    }
 
    public System.Func<string, bool, T> FactoryT
    {
        get;
        set;
    }
}

If you want to see how to do it, you can download the source code here.

Posted by Matthieu MEZIL | 4 comment(s)
Filed under: , ,

WCF with async await logic

I found this very interesting post to get a Task<T> from a WCF call.

I change it a little bit to fix a bug and to allow usage of async / await using AsyncCtpLibrary.

So I propose this implementation:

public static class TaskFactoryExtensions
{
    public static async Task<TResult> ToTask<TResult>(this IAsyncResult asyncResult, Func<IAsyncResult, TResult> endMethod)
    {
        var taskCompletionSource = new TaskCompletionSource<TResult>();
        if (asyncResult.IsCompleted)
            taskCompletionSource.TrySetResult(asyncResult, endMethod);
        else
        {
            object lockObject = new object();
            bool proccessed = false;
            ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle, (state, timeOut) =>
            {
                if (proccessed)
                    return;
                lock (lockObject)
                {
                    if (proccessed)
                        return;
                    proccessed = true;
                }
                taskCompletionSource.TrySetResult(asyncResult, endMethod);
            }, null, -1, true);
            if (asyncResult.IsCompleted)
            {
                lock (lockObject)
                {
                    if (!proccessed)
                    {
                        proccessed = true;
                        taskCompletionSource.TrySetResult(asyncResult, endMethod);
                    }
                }
            }
        }
        TResult value = await taskCompletionSource.Task;
        return value;
    }
 
    private static void TrySetResult<TResult>(this TaskCompletionSource<TResult> taskCompletionSource, IAsyncResult asyncResult, Func<IAsyncResult, TResult> endMethod)
    {
        try
        {
            var result = endMethod(asyncResult);
            taskCompletionSource.TrySetResult(result);
        }
        catch (OperationCanceledException)
        {
            taskCompletionSource.TrySetCanceled();
        }
        catch (Exception e)
        {
            taskCompletionSource.TrySetException(e);
        }
    }
}

So now, I can use this code to call my WCF Service:

private async Task<List<Customer>> GetCustomers()
{
    var service = new MyService();
    return await service.BeginGetCustomers(null, null).ToTask(ar => service.EndGetCustomers(ar));
}

Enjoy! :)

 

Update : With AsyncCtpLibrary, we could just do it:

return await Task.Factory.FromAsync(service.BeginGetCustomers(null, null), ar => service.EndGetCustomers(ar));

Posted by Matthieu MEZIL | with no comments
Filed under: , , ,

WPF/SL: lazy loading TreeView

01/26/2012: Code update

Imagine the following scenario: you have a WCF service with two methods:

List<Customer> GetCustomers();
List<Order> GetOrders(int CustomerId);

You want a treeview with lazy loading in a WPF Window.

There is many way to do it.

I identify three main in my searches:

  • you can use event on your treeview implemented in code-behind
  • you can makes your TreeView control inheriting the framework one’s
  • you can use all the logic on ViewModels and use binding

The last point is realized by adding a CustomerViewModel, having a collection of CustomerViewModel in the VM that encapsulated a Customer and adding IsExpanded property and add the logic of loading orders.

It’s a way often saw in the web and that seems a good way with MVVM for many developers but I think, IMHO, it is NOT a good way.

Indeed, what happens if under Orders, I want OrderDetails? You will add a new OrderViewModel class that encapsulates an Order and the CustomerViewModel class will have a collection of OrderViewModel?

I don’t want to make again my Model in my ViewModel.

I could use the ICustomTypeDescriptor (ICustomTypeProvider in SL) as I did here but I think that if this solution is interesting to add business logic on entity, it is not to add control logic.

I think that the lazy loading control logic should be encapsulated in a behavior and the ViewModel should just have the lazy loading WCF calls logic.

So, I use an ILazyLoader interface:

public interface ILazyLoader
{

    string GetChildPropertyName(object obj);

    bool IsLoaded(object obj);
    void Load(object obj);
}

and an implementation of it using delegate:

public class LazyLoader : ILazyLoader
{
    private Func<object, string> _getChildPropertyName;
    private Func<object, bool> _isLoaded;
    private Action<object> _load;
 
    public LazyLoader(Func<object, string> getChildPropertyName, Func<object, bool> isLoaded, Action<object> load)
    {
        _getChildPropertyName = getChildPropertyName;
        _isLoaded = isLoaded;
        _load = load;
    }
 
    public string GetChildPropertyName(object obj)
    {
        return _getChildPropertyName(obj);
    }
 
    public bool IsLoaded(object obj)
    {
        return _isLoaded(obj);
    }
 
    public void Load(object obj)
    {
        _load(obj);
    }
}

Then, in my ViewModel, I use the following code:

public class CustomerViewModel
{
    private ObservableCollection<Customer> _customers;
    public ObservableCollection<Customer> Customers
    {
        get
        {
            if (_customers == null)
            {
                _customers = new ObservableCollection<Customer>();
                var customersService = new CustomerServiceClient();
                EventHandler<GetCustomersCompletedEventArgs> serviceGetCustomersCompleted = null;
                serviceGetCustomersCompleted = (sender, e) =>
                    {
                        customersService.GetCustomersCompleted -= serviceGetCustomersCompleted;
                        foreach (var ht in e.Result)
                            _customers.Add(ht);
                    };
                customersService.GetCustomersCompleted += serviceGetCustomersCompleted;
                customersService.GetCustomersAsync();
            }
            return _customers;
        }
    }
 
    private ILazyLoader _lazyLoader;
    public ILazyLoader LazyLoader
    {
        get { return _lazyLoader ?? (_lazyLoader = new LazyLoader(obj => 
            {
                if (obj is HardwareType)
                    return PropertyName.GetPropertyName((Expression<Func<HardwareType, object>>)(ht => ht.Hardwares));
                return null;
            }, obj => _loadedHardwareTypes.Contains((HardwareType)obj), obj => LoadHardwares((HardwareType)obj))); }
    }
 
    private List<Customer> _loadedCustomers = new List<Customer>();
    private void LoadOrders(Customer c)
    {
        var customerService = new CustomerServiceClient();
        c.Orders.Clear();
        EventHandler<GetOrdersCompletedEventArgs> serviceGetOrdersCompleted = null;
        serviceGetOrdersCompleted = (sender, e) =>
        {
            customerService.GetOrdersCompleted -= serviceGetOrdersCompleted;
            foreach (var o in e.Result)
                c.Orders.Add(o);
            _loadedCustomers.Add(c);
        };
        customerService.GetOrdersCompleted += serviceGetCustomersCompleted;
        customerService.GetOrdersAsync(c.Id);
    }
}

Now, this is the code of my behavior:

public static class LazyLoadTreeViewItemBehavior
{
    public static ILazyLoader GetLazyLoader(DependencyObject obj)
    {
        return (ILazyLoader)obj.GetValue(LazyLoaderProperty);
    }
    public static void SetLazyLoader(DependencyObject obj, ILazyLoader value)
    {
        obj.SetValue(LazyLoaderProperty, value);
    }
    public static readonly DependencyProperty LazyLoaderProperty =
        DependencyProperty.RegisterAttached("LazyLoader", typeof(ILazyLoader), typeof(LazyLoadTreeViewItemBehavior), new PropertyMetadata(ApplyingLazyLoadingLogic));
 
    private static void ApplyingLazyLoadingLogic(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var tvi = o as TreeViewItem;
        if (tvi == null)
            throw new InvalidOperationException();
        ILazyLoader lazyLoader= GetLazyLoader(o);
        PropertyInfo childrenProp;
        if (lazyLoader == null)
            return;
        object itemValue = tvi.DataContext;
        string childrenPropName = lazyLoader.GetChildPropertyName(itemValue);
        if (childrenPropName == null || (childrenProp = itemValue.GetType().GetProperty(childrenPropName)) == null)
            return;
        IEnumerable children = (IEnumerable)childrenProp.GetValue(itemValue, null);
        RoutedEventHandler tviExpanded = null;
        RoutedEventHandler tviUnloaded = null;
        tviExpanded = (sender, e2) =>
            {
                tvi.Expanded -= tviExpanded;
                tvi.Unloaded -= tviUnloaded;
if (!lazyLoader.IsLoaded(itemValue))
                {
                    lazyLoader.Load(itemValue);
                    tvi.Items.Clear();
                    tvi.ItemsSource = children;
                }
            };
        tviUnloaded = (sender, e2) =>
            {
                tvi.Expanded -= tviExpanded;
                tvi.Unloaded -= tviUnloaded;
            };
        if (!children.GetEnumerator().MoveNext())
        {
            tvi.ItemsSource = null;
            tvi.Items.Add(new TreeViewItem());
        }
        tvi.Expanded += tviExpanded;
        tvi.Unloaded += tviUnloaded;
    }
}

The thing very interesting with it is the fact that my behavior is not dependent of my model or my ViewModel and can be used with other lazy loading TreeViews.

To do it, I just have to apply our behavior into our TreeView, what can be done in xaml:

<TreeView ItemsSource="{Binding Customers}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="local:LazyLoadTreeViewItemBehavior.LazyLoader" 
                    Value="{Binding DataContext.LazyLoader, RelativeSource={RelativeSource AncestorType=local:CustomersWindow}}" />
        </Style>
    </TreeView.ItemContainerStyle>
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate>
            <HierarchicalDataTemplate.ItemTemplate>
                <DataTemplate>
                    …
                </DataTemplate>
            </HierarchicalDataTemplate.ItemTemplate>
            …
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

 

I really like this way. What do you think about it?

 

Of course, I write my sample with WPF but it’s still true with SL.

 

Hope this helps…

Posted by Matthieu MEZIL | 2 comment(s)
Filed under: , ,

SL5 with AsyncCtp: The type 'System.Threading.Tasks.Task' exists in both mscorlib.dll and AsyncCtpLibrary_Silverlight.dll

If you use AsyncCtp With SL5, you could have the following issue: The type 'System.Threading.Tasks.Task' exists in both mscorlib.dll and AsyncCtpLibrary_Silverlight.dll.

So you can’t compile your project if you use Task class!

In this case, you could use an alias on the assembly but, you could not use async / await keywords.

I found a trick to do it: using two aliases on AsyncCtpLibrary_Silverlight. For this, in the aliases property of the assembly reference, I use ā€œglobal,AsyncCtpLibraryā€ instead of only ā€œglobalā€.

Like this, I can use async/await because I have global alias on AsyncCtpLibrary_Silverlight.dll and I can use Task class with the following using:

extern alias AsyncCtpLibrary;


using AsyncCtpLibrary.System.Threading.Tasks;

 

Update:

Note that if you use AsyncCtpLibrary_Silverlight5.dll instead of AsyncCtpLibrary_Silverlight.dll, you don’t need to do it!

Posted by Matthieu MEZIL | 2 comment(s)
Filed under: ,

Coding for fun: Write your code in comments with Roslyn

Sometimes, you have some very basic methods and you could find useless to comment them but sometimes you have to comment them in order to generate help file.

Some tools allow you to generate comments using the method signature.

For fun, I will use another way: I will write the method code in the comment.

I have a ConsoleApplication:

class Program
{
    
static void Main(string
[] args)
     {
        
Console.WriteLine("a ?"
);
        
int a = int.Parse(Console
.ReadLine());
        
Console.WriteLine("b ?"
);
        
int b = int.Parse(Console
.ReadLine());
        
Console.WriteLine("{0} + {1} = {2}"
, a, b, Add(a, b));
        
Console
.ReadLine();
     }



/// <summary> /// return a + b; /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <returns></returns> public static int Add(int a, int
b)
{
        
throw new NotImplementedException();
} }

The code of the Add method (return a + b;) is commented.

Now I will write my own C# compiler using Roslyn:

class Program
{
    
static void Main(string
[] args)
     {
        
string
solutionPath = args[0];
        
string
binaryDirectory = args[1];
        
var solution = Solution
.Load(solutionPath);
        
List<IProject
> notSortedProjects = solution.Projects.ToList();
        
List<IProject> projects = new List<IProject
>();
        
do
         {
            
foreach (IProject project in
notSortedProjects.ToList())
             {
                
if
(project.ProjectReferences.All(prId => projects.Any(p => p.Id == prId)))
                 {
                     projects.Add(project);
                     notSortedProjects.Remove(project);
                 }
             }
         }
while
(notSortedProjects.Count != 0);


        
foreach (var project in
projects)
         {
            
foreach (var document in
project.Documents)
             {
                
var
documentTree = document.GetSyntaxTree();
                
var newDocumentTree = new CodeInCommentsRewriter().Visit((SyntaxNode
)documentTree.Root);
                
var newDocumentTreeText = new StringText
(newDocumentTree.ToString());
                 solution = solution.UpdateDocument(document.Id, newDocumentTreeText);
             }
            
using (var stream = new FileStream(string.Format("{0}.{1}"
,
                 
Path
.Combine(binaryDirectory, project.AssemblyName),
                  project.CompilationOptions.AssemblyKind ==
AssemblyKind.DynamicallyLinkedLibrary ? "dll" : "exe"), FileMode
.Create))
             {
                
var
emitResult = solution.Projects.First(p => p.Id == project.Id).GetCompilation().Emit(stream);
                
if
(!emitResult.Success)
                    
throw new InvalidOperationException();
             }
         }
     } }
public class CodeInCommentsRewriter : SyntaxRewriter
{
    
protected override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax
node)
     {
        
IEnumerator<SyntaxNode
> bodySyntaxNodesEnumerator = node.BodyOpt.ChildNodes().GetEnumerator();
        
if
(bodySyntaxNodesEnumerator.MoveNext())
         {
            
ThrowStatementSyntax childNode = bodySyntaxNodesEnumerator.Current as ThrowStatementSyntax
;
            
if (!bodySyntaxNodesEnumerator.MoveNext()// only one child node                 && childNode != null
)
             {
                
var comment = node.GetLeadingTrivia().Select(t => Regex.Match(t.GetText(), "return (.?)*").Value).FirstOrDefault(v => ! string
.IsNullOrEmpty(v));
                
if (comment != null
)
                 {
                    
return Syntax
.MethodDeclaration(
                         node.Attributes,
                         node.Modifiers,
                         node.ReturnType,
                         node.ExplicitInterfaceSpecifierOpt,
                         node.Identifier,
                         node.TypeParameterListOpt,
                         node.ParameterList,
                         node.ConstraintClauses,
                        
Syntax
.Block(
                             node.BodyOpt.OpenBraceToken,
                            
Syntax
.List(
                                
Syntax
.ParseStatement(comment)),
                             node.BodyOpt.CloseBraceToken),
                         node.SemicolonTokenOpt);
                 }
             }
         }
        
return base.VisitMethodDeclaration(node);
     } }

And here we are! // I use some shortcut (the code is a return in one line)

If I execute my compiler and then the generated exe, I can see in the Console that 1 + 2 = 3.

Posted by Matthieu MEZIL | with no comments
Filed under: , ,

EF: Why Include method is an anti-pattern IMHO? Conclusion

First, I admit that Anti-pattern word was excessive but I see so many catastrophic performances in my audits where developers use Include method.

I presented you a very Ā« special Ā» way to realize Include. If you use Include ONLY on the one side, performance difference is not huge and my code is really longer and more complex. But I consider that my solution would become very interesting if this code could be generated.

In a future post, I will share with you a T4 that do the job. Sourire

Note that if my DB was not local but far (SQL Azure for example), difference would be really better for my solution. Indeed, bytes received from server are really much important using Include method because you will probably have duplicated data if you have more than one row.

The main idea of these posts was not to give you a better way to write Include but to help you to realize what happens when you use Include method and what can we do if performance is something very important on your project.

Just note that, as I already mentioned, with my code, you will lose the IQueryable.

Note also that Include method is perfect for table splitting.

You also have to take in consideration that, because I don’t use DB lock, I can have incoherence with my way contrary to Include method. For example, if you first get Customers and the someone adds a new OnlineSale with a new Customer, and then you get the OnlineSales, you will get an OnlineSale without its Customer.

Posted by Matthieu MEZIL | with no comments

EF: Why Include method is an anti-pattern IMHO? 3/3

I will continue on my sample with Contoso.

I won’t use a Repository and I will directly querying my DB on my ViewModel. The main reason is because it is easier for demo and because the main topic is the Include method.

I will use Include method with the best case for it: navigation property to one side.

Imagine that you want to use a WPF DataGrid read only based on OnlineSales where you want to see the OnlineSale.SalesQuantity, OnlineSale.SalesAmount, Customer.LastName, Customer.FirstName, Store.StoreName, Product.ProductName, ProductSubcategory.ProductSubcategoryName and ProductCategory.ProductCategoryName.

This is typically a case where most of developers (and sessions I saw) use the Include method.

public class OnlineOrdersViewModel : INotifyPropertyChanged, IDisposable
{
    private ContosoRetailDWEntities _context = new ContosoRetailDWEntities();
 
    private ObservableCollection<OnlineSale> _onlineSales;
    public ObservableCollection<OnlineSale> OnlineSales
    {
        get 
        {
            if (_onlineSales == null)
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();
                _onlineSales = new ObservableCollection<OnlineSale>(_context.OnlineSales.Take(100).Include(os => os.Customer).Include(os => os.Product.ProductSubcategory.ProductCategory).Include(os => os.Store));
                sw.Stop();
                ElapsedMilliseconds = sw.ElapsedMilliseconds;
            }
            return _onlineSales; 
        }
    }
 
    private long _elapsedMilliseconds;
    public long ElapsedMilliseconds
    {
        get { return _elapsedMilliseconds; }
        set
        {
            _elapsedMilliseconds = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("ElapsedMilliseconds"));
        }
    }
 
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_context != null)
            {
                _context.Dispose();
                _context = null;
            }
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
}

 

The OnlineSales loading needs 1135ms.

IMHO, this is an anti-pattern. Why loading complete entities only for one or two properties?

Here you can use a OnlineSaleDTO that has only these properties. It would be the easier.

You can also use your entities. But in this case, how to do it?

There is one restriction with L2E: you can’t use a new on en entity (I hope it will be fix in the future).

So I use this code:

_onlineSales = new ObservableCollection<OnlineSale>(
    _context.OnlineSales.Take(100).
    Select(os =>
        new
        {
            os.OnlineSalesKey,
            os.SalesQuantity,
            os.SalesAmount,
            os.Customer.LastName,
            os.Customer.FirstName,
            os.Product.ProductName,
            os.Product.ProductSubcategory.ProductSubcategoryName,
            os.Product.ProductSubcategory.ProductCategory.ProductCategoryName,
            os.Store.StoreName
        }).
    AsEnumerable().
    Select(os =>
        new OnlineSale
        {
            OnlineSalesKey = os.OnlineSalesKey,
            SalesQuantity = os.SalesQuantity,
            SalesAmount = os.SalesAmount,
            Customer = new Customer
            {
                LastName = os.LastName,
                FirstName = os.FirstName,
            },
            Product = new Product
            {
                ProductName = os.ProductName,
                ProductSubcategory = (os.ProductSubcategoryName == null ? null : new ProductSubcategory
                {
                    ProductSubcategoryName = os.ProductSubcategoryName,
                    ProductCategory = (os.ProductCategoryName == null ? null : new ProductCategory
                    {
                        ProductCategoryName = os.ProductCategoryName
                    })
                })
            },
            Store = new Store
            {
                StoreName = os.StoreName
            }
        }));

 

Note that because ProductSubcategory.ProductSubcategoryName and ProductCategory.ProductCategoryName properties are not nullable, I can used it in order to know if ProductSubCategory or ProductCategory ar null.

Mine needs only 518 ms so more than twice fast!

Note that if you compare performance with my previous post, you can see that these performances are very bad comparing to what they were. In fact it’s because in my previous post, query execution plans were on SQL Server cache. Here it was not the case. In this case the comparison is really on favor of my way.

When execution plans are in SQL Server cache, Include method needs 507ms and mine 350. So the ratio is less important in this case.

 

Now what happens if the grid is not read only. In 90 % of cases, you won’t modify the related entities but only the OnlineSale entity.

So we will use a TextBox for SalesQuantity and SalesAmount, a ComboBox for Customer, Product and Store.

<DataGrid ItemsSource="{Binding OnlineSales}"
            AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding SalesQuantity}" />
        <DataGridTextColumn Binding="{Binding SalesAmount}" />
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding Path=DataContext.Customers, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                                SelectedItem="{Binding Customer}">
                        <ComboBox.ItemTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Text="{Binding LastName}"
                                                Margin="0,0,5,0" />
                                    <TextBlock Text="{Binding FirstName}" />
                                </StackPanel>
                            </DataTemplate>
                        </ComboBox.ItemTemplate>
                    </ComboBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding Path=DataContext.Stores, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                                DisplayMemberPath="StoreName"
                                SelectedItem="{Binding Store}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding Path=DataContext.Products, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                                SelectedItem="{Binding Product}">
                        <ComboBox.ItemTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Text="{Binding ProductSubcategory.ProductCategory.ProductCategoryName}"
                                                Margin="0,0,5,0" />
                                    <TextBlock Text="/"
                                                Margin="0,0,5,0" />
                                    <TextBlock Text="{Binding ProductSubcategory.ProductSubcategoryName}"
                                                Margin="0,0,5,0" />
                                    <TextBlock Text="/"
                                                Margin="0,0,5,0" />
                                    <TextBlock Text="{Binding ProductName}" />
                                </StackPanel>
                            </DataTemplate>
                        </ComboBox.ItemTemplate>
                    </ComboBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

 

On my audits I often see this code for ViewModel:

public class OnlineOrdersViewModel : INotifyPropertyChanged, IDisposable
{
    private ContosoRetailDWEntities _context = new ContosoRetailDWEntities();
 
    public OnlineOrdersViewModel()
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        // In Real life don't do it in the constructor but in another thread in order to not freeze UI
        _onlineSales = new ObservableCollection<OnlineSale>(_context.OnlineSales.Take(100).Include(os => os.Customer).Include(os => os.Product.ProductSubcategory.ProductCategory).Include(os => os.Store));
        _customers = new ObservableCollection<Customer>(_context.Customers);
        _stores = new ObservableCollection<Store>(_context.Stores);
        _products = new ObservableCollection<Product>(_context.Products.Include(p => p.ProductSubcategory.ProductCategory));
        sw.Stop();
        ElapsedMilliseconds = sw.ElapsedMilliseconds;
    }
 
    private ObservableCollection<OnlineSale> _onlineSales;
    public ObservableCollection<OnlineSale> OnlineSales
    {
        get { return _onlineSales ?? (_onlineSales = new ObservableCollection<OnlineSale>()); }
    }
 
    private ObservableCollection<Customer> _customers;
    public ObservableCollection<Customer> Customers
    {
        get { return _customers ?? (_customers = new ObservableCollection<Customer>()); }
    }
    private ObservableCollection<Product> _products;
    public ObservableCollection<Product> Products
    {
        get { return _products ?? (_products = new ObservableCollection<Product>()); }
    }
    private ObservableCollection<Store> _stores;
    public ObservableCollection<Store> Stores
    {
        get { return _stores ?? (_stores = new ObservableCollection<Store>()); }
    }
 
 
    private long _elapsedMilliseconds;
    public long ElapsedMilliseconds
    {
        get { return _elapsedMilliseconds; }
        set
        {
            _elapsedMilliseconds = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("ElapsedMilliseconds"));
        }
    }
 
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_context != null)
            {
                _context.Dispose();
                _context = null;
            }
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
}

 

This is useless to use Include method when you load OnlineSales because, with foreign keys, EF ObjectContext will attach navigation property alone because we load all related elements (when we load customers, stores, products with ProductSubcategory with ProductCategory).

This first sample needs 2338 ms for loading and 1244 when SQL Server uses its cache.

Now if we remove the useless Include:

_onlineSales = new ObservableCollection<OnlineSale>(_context.OnlineSales.Take(100));
_customers = new ObservableCollection<Customer>(_context.Customers);
_stores = new ObservableCollection<Store>(_context.Stores);
_products = new ObservableCollection<Product>(_context.Products.Include(p => p.ProductSubcategory.ProductCategory));

 

With it, loading needs 1685 ms and 1152 ms when SQL Server uses its cache.

Now I will try to do it with my way loading needs 1459 ms and 1007 ms when SQL Server uses its cache.

object lockObject = new object();
Task onlineSalesTask = new Task(() =>
    {
        using (var context2 = new ContosoRetailDWEntities())
        {
            _onlineSales = new ObservableCollection<OnlineSale>(
                context2.OnlineSales.Take(100).
                Select(os =>
                    new
                    {
                        os.OnlineSalesKey,
                        os.SalesQuantity,
                        os.SalesAmount,
                        os.CustomerKey,
                        os.ProductKey,
                        os.StoreKey
                    }).
                AsEnumerable().
                Select(os =>
                    new OnlineSale
                    {
                        OnlineSalesKey = os.OnlineSalesKey,
                        SalesQuantity = os.SalesQuantity,
                        SalesAmount = os.SalesAmount,
                        CustomerKey = os.CustomerKey,
                        ProductKey = os.ProductKey,
                        StoreKey = os.StoreKey,
                    }));
        }
        lock (lockObject)
        {
            foreach (var os in _onlineSales)
                _context.OnlineSales.Attach(os);
        }
    });
onlineSalesTask.Start();
 
Task customersTask = new Task(() =>
    {
        using (var context2 = new ContosoRetailDWEntities())
        {
            _customers = new ObservableCollection<Customer>(
                _context.Customers.
                Select(c => new
                {
                    c.CustomerKey,
                    c.LastName,
                    c.FirstName
                }).
                AsEnumerable().
                Select(c =>
                    new Customer
                    {
                        CustomerKey = c.CustomerKey,
                        LastName = c.LastName,
                        FirstName = c.FirstName
                    }));
        }
        lock(lockObject)
        {
            foreach (var c in _customers)
                _context.Customers.Attach(c);
        }
    });
customersTask.Start();
 
Task productsTask = new Task(() =>
    {
        _products = new ObservableCollection<Product>(
            _context.Products.
            Select(p => new
            {
                p.ProductKey,
                p.ProductName,
                p.ProductSubcategoryKey
            }).
            AsEnumerable().
            Select(p =>
                new Product
                {
                    ProductKey = p.ProductKey,
                    ProductName = p.ProductName,
                    ProductSubcategoryKey = p.ProductSubcategoryKey
                }));
        lock(lockObject)
        {
            foreach (var p in _products)
                _context.Products.Attach(p);
        }
    });
productsTask.Start();
 
Task productSubCategoriesTask = new Task(() =>
{
    List<ProductSubcategory> productSubCategories;
    using (var context2 = new ContosoRetailDWEntities())
    {
        productSubCategories = _context.ProductSubcategories.
            Select(psc => new
            {
                psc.ProductSubcategoryKey,
                psc.ProductSubcategoryName,
                psc.ProductCategoryKey
            }).
            AsEnumerable().
            Select(psc =>
                new ProductSubcategory
                {
                    ProductSubcategoryKey = psc.ProductSubcategoryKey,
                    ProductSubcategoryName = psc.ProductSubcategoryName,
                    ProductCategoryKey = psc.ProductCategoryKey
                }).
            ToList();
    }
    foreach (var psc in productSubCategories)
        lock (lockObject)
        {
            _context.ProductSubcategories.Attach(psc);
        }
});
productSubCategoriesTask.Start();
 
Task productCategoriesTask = new Task(() =>
{
    List<ProductCategory> productCategories;
    using (var context2 = new ContosoRetailDWEntities())
    {
        productCategories = _context.ProductCategories.
            Select(psc => new
            {
                psc.ProductCategoryKey,
                psc.ProductCategoryName
            }).
            AsEnumerable().
            Select(psc =>
                new ProductCategory
                {
                    ProductCategoryKey = psc.ProductCategoryKey,
                    ProductCategoryName = psc.ProductCategoryName
                }).
            ToList();
    }
    lock (lockObject)
    {
        foreach (var psc in productCategories)
            _context.ProductCategories.Attach(psc);
    }
});
productCategoriesTask.Start();
 
Task storesTask = new Task(() =>
    {
        using (var context2 = new ContosoRetailDWEntities())
        {
            _stores = new ObservableCollection<Store>(
                _context.Stores.
                Select(s => new
                {
                    s.StoreKey,
                    s.StoreName
                }).
                AsEnumerable().
                Select(s =>
                    new Store
                    {
                        StoreKey = s.StoreKey,
                        StoreName = s.StoreName
                    }));
        }
        lock (lockObject)
        {
            foreach (var s in _stores)
                _context.Stores.Attach(s);
        }
    });
storesTask.Start();
 
Task.WaitAll(onlineSalesTask, customersTask, productsTask, productSubCategoriesTask, productCategoriesTask, storesTask);

 

You have to take in consideration that ratio between my way and Include method one would be more important if the DB was not local.

 

Using Include, we have 7755210 bytes received from server. With my way, we only have 853693 bytes.

EF: Why Include method is an anti-pattern IMHO even with many to one navigation properties? 2/3

In my yesterday post, I explained why I think Include method is an anti-pattern.

Darrel commented that the issue was because I use Include with one to many navigation property on many side.

In the sample, I have only one Include to side many and four to side (zero or) one.

But Darrel is right. If I use only one side, Include is not so bad. Not so bad but not the best one anyway.

I will take 2 samples to illustrate it.

In these samples, I will compare time to execute the query from the .NET code (so including SQL generation, SQL execution and EF materialization).

Using Include method my first sample is the following:

private static OnlineSale GetOnlineSaleWithGraph(ContosoRetailDWEntities context)
{
    return context.OnlineSales.Include(os => os.Customer).Include(os => os.Product.ProductSubcategory.ProductCategory).Include(os => os.Store).FirstOrDefault();
}

 

Now with my way, the code is the following:

private static OnlineSale GetOnlineSaleWithGraph(ContosoRetailDWEntities context)
{
    OnlineSale onlineSale = null;
    object lockObject = new object();
    Task onlineSaleTask = new Task(() =>
        {
            using (var context2 = new ContosoRetailDWEntities())
            {
                var query = context2.OnlineSales;
                ObjectQuery<OnlineSale> objectQuery = (ObjectQuery<OnlineSale>)query;
                objectQuery.MergeOption = MergeOption.NoTracking;
                onlineSale = objectQuery.FirstOrDefault();
            }
            if (onlineSale != null)
                lock (lockObject)
                {
                    context.OnlineSales.Attach(onlineSale);
                }
        });
    onlineSaleTask.Start();
 
    Task customerTask = new Task(() =>
        {
            Customer customer;
            using (var context2 = new ContosoRetailDWEntities())
            {
                var query = context2.OnlineSales.Select(os => os.Customer);
                ObjectQuery<Customer> objectQuery = (ObjectQuery<Customer>)query;
                objectQuery.MergeOption = MergeOption.NoTracking;
                customer = objectQuery.FirstOrDefault();
            }
            if (customer != null)
                lock (lockObject)
                {
                    context.Customers.Attach(customer);
                }
        });
    customerTask.Start();
 
    Task productTask = new Task(() =>
    {
        using (var context2 = new ContosoRetailDWEntities())
        {
            ObjectQuery<OnlineSale> objectQuery = context2.OnlineSales;
            objectQuery.MergeOption = MergeOption.NoTracking;
            var query = objectQuery.Select(os => new { os.Product, os.Product.ProductSubcategory, os.Product.ProductSubcategory.ProductCategory });
            var product = query.FirstOrDefault();
            if (product != null)
                lock (lockObject)
                {
                    context.Products.Attach(product.Product);
                    if (product.ProductSubcategory != null)
                    {
                        context.ProductSubcategories.Attach(product.ProductSubcategory);
                        if (product.ProductCategory != null)
                            context.ProductCategories.Attach(product.ProductCategory);
                    }
                }
        }
    });
    productTask.Start();
 
    Task storeTask = new Task(() =>
    {
        Store store;
        using (var context2 = new ContosoRetailDWEntities())
        {
            var query = context2.OnlineSales.Select(os => os.Store);
            ObjectQuery<Store> objectQuery = (ObjectQuery<Store>)query;
            objectQuery.MergeOption = MergeOption.NoTracking;
            store = objectQuery.FirstOrDefault();
        }
        if (store != null)
            lock (lockObject)
            {
                context.Stores.Attach(store);
            }
    });
    storeTask.Start();
 
    Task.WaitAll(onlineSaleTask, customerTask, productTask, storeTask);
 
    return onlineSale;
}

 

As you can see, I use a sort of Include to load ProductSubCategory and ProductCategory with the Product. I do it because as I get only one product, I’m sure that I won’t duplicate ProductSubCategory or ProductCategory.

In my tests, the code using Include run on 962 ms and 611 ms when the execution plan is on SQL Server cache and mine in 749 ms (22 % faster) and 548 ms when SQL Server uses its cache (10% faster).

Now I will do the same getting 100 OnlineSales

private static List<OnlineSale> GetTenLastOnlineSalesWithGraph(ContosoRetailDWEntities context)
{
    return context.OnlineSales.Take(100).Include(os => os.Customer).Include(os => os.Product.ProductSubcategory.ProductCategory).Include(os => os.Store).ToList();
}

 

Now my code is the following:

private static List<OnlineSale> GetTenLastOnlineSalesWithGraph(ContosoRetailDWEntities context)
{
    List<OnlineSale> onlineSales = null;
    object lockObject = new object();
    Task onlineSalesTask = new Task(() =>
        {
            using (var context2 = new ContosoRetailDWEntities())
            {
                var query = context2.OnlineSales.Take(100);
                ObjectQuery<OnlineSale> objectQuery = (ObjectQuery<OnlineSale>)query;
                objectQuery.MergeOption = MergeOption.NoTracking;
                onlineSales = objectQuery.ToList();
            }
            lock (lockObject)
            {
                foreach (var onlineSale in onlineSales)
                    context.OnlineSales.Attach(onlineSale);
            }
        });
    onlineSalesTask.Start();
 
    Task customersTask = new Task(() =>
        {
            List<Customer> customers;
            using (var context2 = new ContosoRetailDWEntities())
            {
                var query = context2.OnlineSales.Take(100).Select(os => os.Customer).Distinct();
                ObjectQuery<Customer> objectQuery = (ObjectQuery<Customer>)query;
                objectQuery.MergeOption = MergeOption.NoTracking;
                customers = objectQuery.ToList();
            }
            lock (lockObject)
            {
                foreach (var customer in customers)
                    context.Customers.Attach(customer);
            }
        });
    customersTask.Start();
 
    Task productsTask = new Task(() =>
    {
        List<Product> products;
        using (var context2 = new ContosoRetailDWEntities())
        {
            var query = context2.OnlineSales.Take(100).Select(os => os.Product).Distinct();
            ObjectQuery<Product> objectQuery = (ObjectQuery<Product>)query;
            objectQuery.MergeOption = MergeOption.NoTracking;
            products = objectQuery.ToList();
        }
        lock (lockObject)
        {
            foreach (var product in products)
                context.Products.Attach(product);
        }
    });
    productsTask.Start();
 
    Task productSubCategoriesTask = new Task(() =>
    {
        List<ProductSubcategory> productSubCategories;
        using (var context2 = new ContosoRetailDWEntities())
        {
            var query = context2.OnlineSales.Take(100).Select(os => os.Product.ProductSubcategory).Distinct();
            ObjectQuery<ProductSubcategory> objectQuery = (ObjectQuery<ProductSubcategory>)query;
            objectQuery.MergeOption = MergeOption.NoTracking;
            productSubCategories = objectQuery.ToList();
        }
        lock (lockObject)
        {
            foreach (var productSubCategory in productSubCategories)
                context.ProductSubcategories.Attach(productSubCategory);
        }
    });
    productSubCategoriesTask.Start();
 
    Task productCategoriesTask = new Task(() =>
    {
        List<ProductCategory> productCategories;
        using (var context2 = new ContosoRetailDWEntities())
        {
            var query = context2.OnlineSales.Take(100).Select(os => os.Product.ProductSubcategory.ProductCategory).Distinct().Distinct();
            ObjectQuery<ProductCategory> objectQuery = (ObjectQuery<ProductCategory>)query;
            objectQuery.MergeOption = MergeOption.NoTracking;
            productCategories = objectQuery.ToList();
        }
        lock (lockObject)
        {
            foreach (var productCategory in productCategories)
                context.ProductCategories.Attach(productCategory);
        }
    });
    productCategoriesTask.Start();
 
    Task storesTask = new Task(() =>
    {
        List<Store> stores;
        using (var context2 = new ContosoRetailDWEntities())
        {
            var query = context2.OnlineSales.Take(100).Select(os => os.Store).Distinct();
            ObjectQuery<Store> objectQuery = (ObjectQuery<Store>)query;
            objectQuery.MergeOption = MergeOption.NoTracking;
            stores = objectQuery.ToList();
        }
        lock (lockObject)
        {
            foreach (var store in stores)
                context.Stores.Attach(store);
        }
    });
    storesTask.Start();
 
    Task.WaitAll(onlineSalesTask, customersTask, productsTask, productSubCategoriesTask, productCategoriesTask, storesTask);
 
    return onlineSales;
}

 

In my tests, the code using Include run on 1118 ms and 657 ms when SQL Server uses its cache and mine in 941 ms (16% faster) and 587 ms (11% faster).

 

It’s interesting to note that, for performance aspect, in many to one navigation property, I prefer starting from my base query and use a Distinct but when I have a many to one navigation property, I prefer starting with my result EntitySet and use a Where with an Any on my base query.

context2.ProductCategories.Where(ca => context2.Customers.Take(50).SelectMany(c => c.OnlineSales).Any(os => os.Product.ProductSubcategory.ProductCategoryKey == ca.ProductCategoryKey));

 

So even like this you can see that Include method has not the best performance even if it is not catastrophic contrary to yesterday.

 

Note that my computer is ā€œonlyā€ a dual core and the DB is local. Else, my way would be more better.

 

In fact, I think that Include is very good if you have a one to one property and if the Include has only one branch per graph depth.

Just note that the Include has something very interesting comparing to my code: Include method returns an IQueryable<T> after using it.

EF: Why Include method is an anti-pattern IMHO?

On different session I saw, eager loading with Include method is presented as a good pattern.

However, IMHO, it’s an anti-pattern.

That’s right, Include method like lazy loading is very easy to use. But performance with these is often very bad or even catastrophic.

One guy of the EF team told me last year, like a joke, that they should named Include IncludeButYouShouldNotUseIt and LazyLoadingEnabledButYouShouldNot.

For a developer, it’s a surprise because Include seems very good. Indeed we have only one query executed on the DB.

However, you have to understand that SQL is not C#.

If you use the Include method, you will get the full graph in only one SQL query.

But, in SQL, we don’t get some objects with relationships. You get a resultset with some datarows with some columns.

This is very important because it means that Include methods generated SQL gets flat data with many duplications.

Take a sample. I will use ContosoRetailDW DB with the following EDM:

clip_image001[4]

Then I will use the following query:

var customers = context.Customers.Take(50).Include(c => c.OnlineSales.Select(os => os.Product.ProductSubcategory.ProductCategory)).Include(c => c.OnlineSales.Select(os => os.Store)).ToList();

 

EF generates the following SQL query:

 

SELECT

[Project1].[CustomerKey] AS [CustomerKey],

[Project1].[GeographyKey] AS [GeographyKey],

[Project1].[CustomerLabel] AS [CustomerLabel],

[Project1].[Title] AS [Title],

[Project1].[FirstName] AS [FirstName],

[Project1].[MiddleName] AS [MiddleName],

[Project1].[LastName] AS [LastName],

[Project1].[NameStyle] AS [NameStyle],

[Project1].[BirthDate] AS [BirthDate],

[Project1].[MaritalStatus] AS [MaritalStatus],

[Project1].[Suffix] AS [Suffix],

[Project1].[Gender] AS [Gender],

[Project1].[EmailAddress] AS [EmailAddress],

[Project1].[YearlyIncome] AS [YearlyIncome],

[Project1].[TotalChildren] AS [TotalChildren],

[Project1].[NumberChildrenAtHome] AS [NumberChildrenAtHome],

[Project1].[Education] AS [Education],

[Project1].[Occupation] AS [Occupation],

[Project1].[HouseOwnerFlag] AS [HouseOwnerFlag],

[Project1].[NumberCarsOwned] AS [NumberCarsOwned],

[Project1].[AddressLine1] AS [AddressLine1],

[Project1].[AddressLine2] AS [AddressLine2],

[Project1].[Phone] AS [Phone],

[Project1].[DateFirstPurchase] AS [DateFirstPurchase],

[Project1].[CustomerType] AS [CustomerType],

[Project1].[CompanyName] AS [CompanyName],

[Project1].[ETLLoadID] AS [ETLLoadID],

[Project1].[LoadDate] AS [LoadDate],

[Project1].[UpdateDate] AS [UpdateDate],

[Project1].[C1] AS [C1],

[Project1].[OnlineSalesKey] AS [OnlineSalesKey],

[Project1].[DateKey] AS [DateKey],

[Project1].[StoreKey] AS [StoreKey],

[Project1].[ProductKey] AS [ProductKey],

[Project1].[PromotionKey] AS [PromotionKey],

[Project1].[CurrencyKey] AS [CurrencyKey],

[Project1].[CustomerKey1] AS [CustomerKey1],

[Project1].[SalesOrderNumber] AS [SalesOrderNumber],

[Project1].[SalesOrderLineNumber] AS [SalesOrderLineNumber],

[Project1].[SalesQuantity] AS [SalesQuantity],

[Project1].[SalesAmount] AS [SalesAmount],

[Project1].[ReturnQuantity] AS [ReturnQuantity],

[Project1].[ReturnAmount] AS [ReturnAmount],

[Project1].[DiscountQuantity] AS [DiscountQuantity],

[Project1].[DiscountAmount] AS [DiscountAmount],

[Project1].[TotalCost] AS [TotalCost],

[Project1].[UnitCost] AS [UnitCost],

[Project1].[UnitPrice] AS [UnitPrice],

[Project1].[ETLLoadID1] AS [ETLLoadID1],

[Project1].[LoadDate1] AS [LoadDate1],

[Project1].[UpdateDate1] AS [UpdateDate1],

[Project1].[ProductKey1] AS [ProductKey1],

[Project1].[ProductLabel] AS [ProductLabel],

[Project1].[ProductName] AS [ProductName],

[Project1].