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 | 2 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 | with no comments
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].[ProductDescription] AS [ProductDescription],

[Project1].[ProductSubcategoryKey] AS [ProductSubcategoryKey],

[Project1].[Manufacturer] AS [Manufacturer],

[Project1].[BrandName] AS [BrandName],

[Project1].[ClassID] AS [ClassID],

[Project1].[ClassName] AS [ClassName],

[Project1].[StyleID] AS [StyleID],

[Project1].[StyleName] AS [StyleName],

[Project1].[ColorID] AS [ColorID],

[Project1].[ColorName] AS [ColorName],

[Project1].[Size] AS [Size],

[Project1].[SizeRange] AS [SizeRange],

[Project1].[SizeUnitMeasureID] AS [SizeUnitMeasureID],

[Project1].[Weight] AS [Weight],

[Project1].[WeightUnitMeasureID] AS [WeightUnitMeasureID],

[Project1].[UnitOfMeasureID] AS [UnitOfMeasureID],

[Project1].[UnitOfMeasureName] AS [UnitOfMeasureName],

[Project1].[StockTypeID] AS [StockTypeID],

[Project1].[StockTypeName] AS [StockTypeName],

[Project1].[UnitCost1] AS [UnitCost1],

[Project1].[UnitPrice1] AS [UnitPrice1],

[Project1].[AvailableForSaleDate] AS [AvailableForSaleDate],

[Project1].[StopSaleDate] AS [StopSaleDate],

[Project1].[Status] AS [Status],

[Project1].[ImageURL] AS [ImageURL],

[Project1].[ProductURL] AS [ProductURL],

[Project1].[ETLLoadID2] AS [ETLLoadID2],

[Project1].[LoadDate2] AS [LoadDate2],

[Project1].[UpdateDate2] AS [UpdateDate2],

[Project1].[ProductSubcategoryKey1] AS [ProductSubcategoryKey1],

[Project1].[ProductSubcategoryLabel] AS [ProductSubcategoryLabel],

[Project1].[ProductSubcategoryName] AS [ProductSubcategoryName],

[Project1].[ProductSubcategoryDescription] AS [ProductSubcategoryDescription],

[Project1].[ProductCategoryKey] AS [ProductCategoryKey],

[Project1].[ETLLoadID3] AS [ETLLoadID3],

[Project1].[LoadDate3] AS [LoadDate3],

[Project1].[UpdateDate3] AS [UpdateDate3],

[Project1].[ProductCategoryKey1] AS [ProductCategoryKey1],

[Project1].[ProductCategoryLabel] AS [ProductCategoryLabel],

[Project1].[ProductCategoryName] AS [ProductCategoryName],

[Project1].[ProductCategoryDescription] AS [ProductCategoryDescription],

[Project1].[ETLLoadID4] AS [ETLLoadID4],

[Project1].[LoadDate4] AS [LoadDate4],

[Project1].[UpdateDate4] AS [UpdateDate4],

[Project1].[StoreKey1] AS [StoreKey1],

[Project1].[GeographyKey1] AS [GeographyKey1],

[Project1].[StoreManager] AS [StoreManager],

[Project1].[StoreType] AS [StoreType],

[Project1].[StoreName] AS [StoreName],

[Project1].[StoreDescription] AS [StoreDescription],

[Project1].[Status1] AS [Status1],

[Project1].[OpenDate] AS [OpenDate],

[Project1].[CloseDate] AS [CloseDate],

[Project1].[EntityKey] AS [EntityKey],

[Project1].[ZipCode] AS [ZipCode],

[Project1].[ZipCodeExtension] AS [ZipCodeExtension],

[Project1].[StorePhone] AS [StorePhone],

[Project1].[StoreFax] AS [StoreFax],

[Project1].[AddressLine11] AS [AddressLine11],

[Project1].[AddressLine21] AS [AddressLine21],

[Project1].[CloseReason] AS [CloseReason],

[Project1].[EmployeeCount] AS [EmployeeCount],

[Project1].[SellingAreaSize] AS [SellingAreaSize],

[Project1].[LastRemodelDate] AS [LastRemodelDate],

[Project1].[ETLLoadID5] AS [ETLLoadID5],

[Project1].[LoadDate5] AS [LoadDate5],

[Project1].[UpdateDate5] AS [UpdateDate5]

FROM ( SELECT

     [Limit1].[CustomerKey] AS [CustomerKey],

     [Limit1].[GeographyKey] AS [GeographyKey],

     [Limit1].[CustomerLabel] AS [CustomerLabel],

     [Limit1].[Title] AS [Title],

     [Limit1].[FirstName] AS [FirstName],

     [Limit1].[MiddleName] AS [MiddleName],

     [Limit1].[LastName] AS [LastName],

     [Limit1].[NameStyle] AS [NameStyle],

     [Limit1].[BirthDate] AS [BirthDate],

     [Limit1].[MaritalStatus] AS [MaritalStatus],

     [Limit1].[Suffix] AS [Suffix],

     [Limit1].[Gender] AS [Gender],

     [Limit1].[EmailAddress] AS [EmailAddress],

     [Limit1].[YearlyIncome] AS [YearlyIncome],

     [Limit1].[TotalChildren] AS [TotalChildren],

     [Limit1].[NumberChildrenAtHome] AS [NumberChildrenAtHome],

     [Limit1].[Education] AS [Education],

     [Limit1].[Occupation] AS [Occupation],

     [Limit1].[HouseOwnerFlag] AS [HouseOwnerFlag],

     [Limit1].[NumberCarsOwned] AS [NumberCarsOwned],

     [Limit1].[AddressLine1] AS [AddressLine1],

     [Limit1].[AddressLine2] AS [AddressLine2],

     [Limit1].[Phone] AS [Phone],

     [Limit1].[DateFirstPurchase] AS [DateFirstPurchase],

     [Limit1].[CustomerType] AS [CustomerType],

     [Limit1].[CompanyName] AS [CompanyName],

     [Limit1].[ETLLoadID] AS [ETLLoadID],

     [Limit1].[LoadDate] AS [LoadDate],

     [Limit1].[UpdateDate] AS [UpdateDate],

     [Join4].[OnlineSalesKey] AS [OnlineSalesKey],

     [Join4].[DateKey] AS [DateKey],

     [Join4].[StoreKey1] AS [StoreKey],

     [Join4].[ProductKey1] AS [ProductKey],

     [Join4].[PromotionKey] AS [PromotionKey],

     [Join4].[CurrencyKey] AS [CurrencyKey],

     [Join4].[CustomerKey] AS [CustomerKey1],

     [Join4].[SalesOrderNumber] AS [SalesOrderNumber],

     [Join4].[SalesOrderLineNumber] AS [SalesOrderLineNumber],

     [Join4].[SalesQuantity] AS [SalesQuantity],

     [Join4].[SalesAmount] AS [SalesAmount],

     [Join4].[ReturnQuantity] AS [ReturnQuantity],

     [Join4].[ReturnAmount] AS [ReturnAmount],

     [Join4].[DiscountQuantity] AS [DiscountQuantity],

     [Join4].[DiscountAmount] AS [DiscountAmount],

     [Join4].[TotalCost] AS [TotalCost],

     [Join4].[UnitCost1] AS [UnitCost],

     [Join4].[UnitPrice1] AS [UnitPrice],

     [Join4].[ETLLoadID1] AS [ETLLoadID1],

     [Join4].[LoadDate1] AS [LoadDate1],

     [Join4].[UpdateDate1] AS [UpdateDate1],

     [Join4].[ProductKey2] AS [ProductKey1],

     [Join4].[ProductLabel] AS [ProductLabel],

     [Join4].[ProductName] AS [ProductName],

     [Join4].[ProductDescription] AS [ProductDescription],

     [Join4].[ProductSubcategoryKey1] AS [ProductSubcategoryKey],

     [Join4].[Manufacturer] AS [Manufacturer],

     [Join4].[BrandName] AS [BrandName],

     [Join4].[ClassID] AS [ClassID],

     [Join4].[ClassName] AS [ClassName],

     [Join4].[StyleID] AS [StyleID],

     [Join4].[StyleName] AS [StyleName],

     [Join4].[ColorID] AS [ColorID],

     [Join4].[ColorName] AS [ColorName],

     [Join4].[Size] AS [Size],

     [Join4].[SizeRange] AS [SizeRange],

     [Join4].[SizeUnitMeasureID] AS [SizeUnitMeasureID],

     [Join4].[Weight] AS [Weight],

     [Join4].[WeightUnitMeasureID] AS [WeightUnitMeasureID],

     [Join4].[UnitOfMeasureID] AS [UnitOfMeasureID],

     [Join4].[UnitOfMeasureName] AS [UnitOfMeasureName],

     [Join4].[StockTypeID] AS [StockTypeID],

     [Join4].[StockTypeName] AS [StockTypeName],

     [Join4].[UnitCost2] AS [UnitCost1],

     [Join4].[UnitPrice2] AS [UnitPrice1],

     [Join4].[AvailableForSaleDate] AS [AvailableForSaleDate],

     [Join4].[StopSaleDate] AS [StopSaleDate],

     [Join4].[Status1] AS [Status],

     [Join4].[ImageURL] AS [ImageURL],

     [Join4].[ProductURL] AS [ProductURL],

     [Join4].[ETLLoadID2] AS [ETLLoadID2],

     [Join4].[LoadDate2] AS [LoadDate2],

     [Join4].[UpdateDate2] AS [UpdateDate2],

     [Join4].[ProductSubcategoryKey2] AS [ProductSubcategoryKey1],

     [Join4].[ProductSubcategoryLabel] AS [ProductSubcategoryLabel],

     [Join4].[ProductSubcategoryName] AS [ProductSubcategoryName],

     [Join4].[ProductSubcategoryDescription] AS [ProductSubcategoryDescription],

     [Join4].[ProductCategoryKey1] AS [ProductCategoryKey],

     [Join4].[ETLLoadID3] AS [ETLLoadID3],

     [Join4].[LoadDate3] AS [LoadDate3],

     [Join4].[UpdateDate3] AS [UpdateDate3],

     [Join4].[ProductCategoryKey2] AS [ProductCategoryKey1],

     [Join4].[ProductCategoryLabel] AS [ProductCategoryLabel],

     [Join4].[ProductCategoryName] AS [ProductCategoryName],

     [Join4].[ProductCategoryDescription] AS [ProductCategoryDescription],

     [Join4].[ETLLoadID4] AS [ETLLoadID4],

     [Join4].[LoadDate4] AS [LoadDate4],

     [Join4].[UpdateDate4] AS [UpdateDate4],

     [Join4].[StoreKey2] AS [StoreKey1],

     [Join4].[GeographyKey] AS [GeographyKey1],

     [Join4].[StoreManager] AS [StoreManager],

     [Join4].[StoreType] AS [StoreType],

     [Join4].[StoreName] AS [StoreName],

     [Join4].[StoreDescription] AS [StoreDescription],

     [Join4].[Status2] AS [Status1],

     [Join4].[OpenDate] AS [OpenDate],

     [Join4].[CloseDate] AS [CloseDate],

     [Join4].[EntityKey] AS [EntityKey],

     [Join4].[ZipCode] AS [ZipCode],

     [Join4].[ZipCodeExtension] AS [ZipCodeExtension],

     [Join4].[StorePhone] AS [StorePhone],

     [Join4].[StoreFax] AS [StoreFax],

     [Join4].[AddressLine1] AS [AddressLine11],

     [Join4].[AddressLine2] AS [AddressLine21],

     [Join4].[CloseReason] AS [CloseReason],

     [Join4].[EmployeeCount] AS [EmployeeCount],

     [Join4].[SellingAreaSize] AS [SellingAreaSize],

     [Join4].[LastRemodelDate] AS [LastRemodelDate],

     [Join4].[ETLLoadID5] AS [ETLLoadID5],

     [Join4].[LoadDate5] AS [LoadDate5],

     [Join4].[UpdateDate5] AS [UpdateDate5],

     CASE WHEN ([Join4].[OnlineSalesKey] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]

     FROM (SELECT TOP (50) [c].[CustomerKey] AS [CustomerKey], [c].[GeographyKey] AS [GeographyKey], [c].[CustomerLabel] AS [CustomerLabel], [c].[Title] AS [Title], [c].[FirstName] AS [FirstName], [c].[MiddleName] AS [MiddleName], [c].[LastName] AS [LastName], [c].[NameStyle] AS [NameStyle], [c].[BirthDate] AS [BirthDate], [c].[MaritalStatus] AS [MaritalStatus], [c].[Suffix] AS [Suffix], [c].[Gender] AS [Gender], [c].[EmailAddress] AS [EmailAddress], [c].[YearlyIncome] AS [YearlyIncome], [c].[TotalChildren] AS [TotalChildren], [c].[NumberChildrenAtHome] AS [NumberChildrenAtHome], [c].[Education] AS [Education], [c].[Occupation] AS [Occupation], [c].[HouseOwnerFlag] AS [HouseOwnerFlag], [c].[NumberCarsOwned] AS [NumberCarsOwned], [c].[AddressLine1] AS [AddressLine1], [c].[AddressLine2] AS [AddressLine2], [c].[Phone] AS [Phone], [c].[DateFirstPurchase] AS [DateFirstPurchase], [c].[CustomerType] AS [CustomerType], [c].[CompanyName] AS [CompanyName], [c].[ETLLoadID] AS [ETLLoadID], [c].[LoadDate] AS [LoadDate], [c].[UpdateDate] AS [UpdateDate]

         FROM [dbo].[DimCustomer] AS [c] ) AS [Limit1]

     LEFT OUTER JOIN (SELECT [Extent2].[OnlineSalesKey] AS [OnlineSalesKey], [Extent2].[DateKey] AS [DateKey], [Extent2].[StoreKey] AS [StoreKey1], [Extent2].[ProductKey] AS [ProductKey1], [Extent2].[PromotionKey] AS [PromotionKey], [Extent2].[CurrencyKey] AS [CurrencyKey], [Extent2].[CustomerKey] AS [CustomerKey], [Extent2].[SalesOrderNumber] AS [SalesOrderNumber], [Extent2].[SalesOrderLineNumber] AS [SalesOrderLineNumber], [Extent2].[SalesQuantity] AS [SalesQuantity], [Extent2].[SalesAmount] AS [SalesAmount], [Extent2].[ReturnQuantity] AS [ReturnQuantity], [Extent2].[ReturnAmount] AS [ReturnAmount], [Extent2].[DiscountQuantity] AS [DiscountQuantity], [Extent2].[DiscountAmount] AS [DiscountAmount], [Extent2].[TotalCost] AS [TotalCost], [Extent2].[UnitCost] AS [UnitCost1], [Extent2].[UnitPrice] AS [UnitPrice1], [Extent2].[ETLLoadID] AS [ETLLoadID1], [Extent2].[LoadDate] AS [LoadDate1], [Extent2].[UpdateDate] AS [UpdateDate1], [Extent3].[ProductKey] AS [ProductKey2], [Extent3].[ProductLabel] AS [ProductLabel], [Extent3].[ProductName] AS [ProductName], [Extent3].[ProductDescription] AS [ProductDescription], [Extent3].[ProductSubcategoryKey] AS [ProductSubcategoryKey1], [Extent3].[Manufacturer] AS [Manufacturer], [Extent3].[BrandName] AS [BrandName], [Extent3].[ClassID] AS [ClassID], [Extent3].[ClassName] AS [ClassName], [Extent3].[StyleID] AS [StyleID], [Extent3].[StyleName] AS [StyleName], [Extent3].[ColorID] AS [ColorID], [Extent3].[ColorName] AS [ColorName], [Extent3].[Size] AS [Size], [Extent3].[SizeRange] AS [SizeRange], [Extent3].[SizeUnitMeasureID] AS [SizeUnitMeasureID], [Extent3].[Weight] AS [Weight], [Extent3].[WeightUnitMeasureID] AS [WeightUnitMeasureID], [Extent3].[UnitOfMeasureID] AS [UnitOfMeasureID], [Extent3].[UnitOfMeasureName] AS [UnitOfMeasureName], [Extent3].[StockTypeID] AS [StockTypeID], [Extent3].[StockTypeName] AS [StockTypeName], [Extent3].[UnitCost] AS [UnitCost2], [Extent3].[UnitPrice] AS [UnitPrice2], [Extent3].[AvailableForSaleDate] AS [AvailableForSaleDate], [Extent3].[StopSaleDate] AS [StopSaleDate], [Extent3].[Status] AS [Status1], [Extent3].[ImageURL] AS [ImageURL], [Extent3].[ProductURL] AS [ProductURL], [Extent3].[ETLLoadID] AS [ETLLoadID2], [Extent3].[LoadDate] AS [LoadDate2], [Extent3].[UpdateDate] AS [UpdateDate2], [Extent4].[ProductSubcategoryKey] AS [ProductSubcategoryKey2], [Extent4].[ProductSubcategoryLabel] AS [ProductSubcategoryLabel], [Extent4].[ProductSubcategoryName] AS [ProductSubcategoryName], [Extent4].[ProductSubcategoryDescription] AS [ProductSubcategoryDescription], [Extent4].[ProductCategoryKey] AS [ProductCategoryKey1], [Extent4].[ETLLoadID] AS [ETLLoadID3], [Extent4].[LoadDate] AS [LoadDate3], [Extent4].[UpdateDate] AS [UpdateDate3], [Extent5].[ProductCategoryKey] AS [ProductCategoryKey2], [Extent5].[ProductCategoryLabel] AS [ProductCategoryLabel], [Extent5].[ProductCategoryName] AS [ProductCategoryName], [Extent5].[ProductCategoryDescription] AS [ProductCategoryDescription], [Extent5].[ETLLoadID] AS [ETLLoadID4], [Extent5].[LoadDate] AS [LoadDate4], [Extent5].[UpdateDate] AS [UpdateDate4], [Extent6].[StoreKey] AS [StoreKey2], [Extent6].[GeographyKey] AS [GeographyKey], [Extent6].[StoreManager] AS [StoreManager], [Extent6].[StoreType] AS [StoreType], [Extent6].[StoreName] AS [StoreName], [Extent6].[StoreDescription] AS [StoreDescription], [Extent6].[Status] AS [Status2], [Extent6].[OpenDate] AS [OpenDate], [Extent6].[CloseDate] AS [CloseDate], [Extent6].[EntityKey] AS [EntityKey], [Extent6].[ZipCode] AS [ZipCode], [Extent6].[ZipCodeExtension] AS [ZipCodeExtension], [Extent6].[StorePhone] AS [StorePhone], [Extent6].[StoreFax] AS

[StoreFax], [Extent6].[AddressLine1] AS [AddressLine1], [Extent6].[AddressLine2] AS [AddressLine2], [Extent6].[CloseReason] AS [CloseReason], [Extent6].[EmployeeCount] AS [EmployeeCount], [Extent6].[SellingAreaSize] AS [SellingAreaSize], [Extent6].[LastRemodelDate] AS [LastRemodelDate], [Extent6].[ETLLoadID] AS [ETLLoadID5], [Extent6].[LoadDate] AS [LoadDate5], [Extent6].[UpdateDate] AS [UpdateDate5]

         FROM [dbo].[FactOnlineSales] AS [Extent2]

         INNER JOIN [dbo].[DimProduct] AS [Extent3] ON [Extent2].[ProductKey] = [Extent3].[ProductKey]

         LEFT OUTER JOIN [dbo].[DimProductSubcategory] AS [Extent4] ON [Extent3].[ProductSubcategoryKey] = [Extent4].[ProductSubcategoryKey]

         LEFT OUTER JOIN [dbo].[DimProductCategory] AS [Extent5] ON [Extent4].[ProductCategoryKey] = [Extent5].[ProductCategoryKey]

         INNER JOIN [dbo].[DimStore] AS [Extent6] ON [Extent2].[StoreKey] = [Extent6].[StoreKey] ) AS [Join4] ON [Limit1].[CustomerKey] = [Join4].[CustomerKey]

) AS [Project1]

ORDER BY [Project1].[CustomerKey] ASC, [Project1].[C1] ASC

The first point is the fact that this query is very bad!

In my computer the client execution time is 122 422 ms so more than 2 minutes! // which often throws a timeout exception

 

With the following query, the client execution time falls to 4161ms!

 

SELECT

     [Limit1].[CustomerKey] AS [CustomerKey],

     [Limit1].[GeographyKey] AS [GeographyKey],

     [Limit1].[CustomerLabel] AS [CustomerLabel],

     [Limit1].[Title] AS [Title],

     [Limit1].[FirstName] AS [FirstName],

     [Limit1].[MiddleName] AS [MiddleName],

     [Limit1].[LastName] AS [LastName],

     [Limit1].[NameStyle] AS [NameStyle],

     [Limit1].[BirthDate] AS [BirthDate],

     [Limit1].[MaritalStatus] AS [MaritalStatus],

     [Limit1].[Suffix] AS [Suffix],

     [Limit1].[Gender] AS [Gender],

     [Limit1].[EmailAddress] AS [EmailAddress],

     [Limit1].[YearlyIncome] AS [YearlyIncome],

     [Limit1].[TotalChildren] AS [TotalChildren],

     [Limit1].[NumberChildrenAtHome] AS [NumberChildrenAtHome],

     [Limit1].[Education] AS [Education],

     [Limit1].[Occupation] AS [Occupation],

     [Limit1].[HouseOwnerFlag] AS [HouseOwnerFlag],

     [Limit1].[NumberCarsOwned] AS [NumberCarsOwned],

     [Limit1].[AddressLine1] AS [AddressLine1],

     [Limit1].[AddressLine2] AS [AddressLine2],

     [Limit1].[Phone] AS [Phone],

     [Limit1].[DateFirstPurchase] AS [DateFirstPurchase],

     [Limit1].[CustomerType] AS [CustomerType],

     [Limit1].[CompanyName] AS [CompanyName],

     [Limit1].[ETLLoadID] AS [ETLLoadID],

     [Limit1].[LoadDate] AS [LoadDate],

     [Limit1].[UpdateDate] AS [UpdateDate],

     [Extent2].[OnlineSalesKey] AS [OnlineSalesKey],

     [Extent2].[DateKey] AS [DateKey],

     [Extent2].[StoreKey] AS [StoreKey],

     [Extent2].[ProductKey] AS [ProductKey],

     [Extent2].[PromotionKey] AS [PromotionKey],

     [Extent2].[CurrencyKey] AS [CurrencyKey],

     [Extent2].[CustomerKey] AS [CustomerKey1],

     [Extent2].[SalesOrderNumber] AS [SalesOrderNumber],

     [Extent2].[SalesOrderLineNumber] AS [SalesOrderLineNumber],

     [Extent2].[SalesQuantity] AS [SalesQuantity],

     [Extent2].[SalesAmount] AS [SalesAmount],

     [Extent2].[ReturnQuantity] AS [ReturnQuantity],

     [Extent2].[ReturnAmount] AS [ReturnAmount],

     [Extent2].[DiscountQuantity] AS [DiscountQuantity],

     [Extent2].[DiscountAmount] AS [DiscountAmount],

     [Extent2].[TotalCost] AS [TotalCost],

     [Extent2].[UnitCost] AS [UnitCost],

     [Extent2].[UnitPrice] AS [UnitPrice],

     [Extent2].[ETLLoadID] AS [ETLLoadID1],

     [Extent2].[LoadDate] AS [LoadDate1],

     [Extent2].[UpdateDate] AS [UpdateDate1],

     [Extent3].[ProductKey] AS [ProductKey1],

     [Extent3].[ProductLabel] AS [ProductLabel],

     [Extent3].[ProductName] AS [ProductName],

     [Extent3].[ProductDescription] AS [ProductDescription],

     [Extent3].[ProductSubcategoryKey] AS [ProductSubcategoryKey],

     [Extent3].[Manufacturer] AS [Manufacturer],

     [Extent3].[BrandName] AS [BrandName],

     [Extent3].[ClassID] AS [ClassID],

     [Extent3].[ClassName] AS [ClassName],

     [Extent3].[StyleID] AS [StyleID],

     [Extent3].[StyleName] AS [StyleName],

     [Extent3].[ColorID] AS [ColorID],

     [Extent3].[ColorName] AS [ColorName],

     [Extent3].[Size] AS [Size],

     [Extent3].[SizeRange] AS [SizeRange],

     [Extent3].[SizeUnitMeasureID] AS [SizeUnitMeasureID],

     [Extent3].[Weight] AS [Weight],

     [Extent3].[WeightUnitMeasureID] AS [WeightUnitMeasureID],

     [Extent3].[UnitOfMeasureID] AS [UnitOfMeasureID],

     [Extent3].[UnitOfMeasureName] AS [UnitOfMeasureName],

     [Extent3].[StockTypeID] AS [StockTypeID],

     [Extent3].[StockTypeName] AS [StockTypeName],

     [Extent3].[UnitCost] AS [UnitCost1],

     [Extent3].[UnitPrice] AS [UnitPrice1],

     [Extent3].[AvailableForSaleDate] AS [AvailableForSaleDate],

     [Extent3].[StopSaleDate] AS [StopSaleDate],

     [Extent3].[Status] AS [Status],

     [Extent3].[ImageURL] AS [ImageURL],

     [Extent3].[ProductURL] AS [ProductURL],

     [Extent3].[ETLLoadID] AS [ETLLoadID2],

     [Extent3].[LoadDate] AS [LoadDate2],

     [Extent3].[UpdateDate] AS [UpdateDate2],

     [Extent4].[ProductSubcategoryKey] AS [ProductSubcategoryKey1],

     [Extent4].[ProductSubcategoryLabel] AS [ProductSubcategoryLabel],

     [Extent4].[ProductSubcategoryName] AS [ProductSubcategoryName],

     [Extent4].[ProductSubcategoryDescription] AS [ProductSubcategoryDescription],

     [Extent4].[ProductCategoryKey] AS [ProductCategoryKey],

     [Extent4].[ETLLoadID] AS [ETLLoadID3],

     [Extent4].[LoadDate] AS [LoadDate3],

     [Extent4].[UpdateDate] AS [UpdateDate3],

     [Extent5].[ProductCategoryKey] AS [ProductCategoryKey1],

     [Extent5].[ProductCategoryLabel] AS [ProductCategoryLabel],

     [Extent5].[ProductCategoryName] AS [ProductCategoryName],

     [Extent5].[ProductCategoryDescription] AS [ProductCategoryDescription],

     [Extent5].[ETLLoadID] AS [ETLLoadID4],

     [Extent5].[LoadDate] AS [LoadDate4],

     [Extent5].[UpdateDate] AS [UpdateDate4],

     [Extent6].[StoreKey] AS [StoreKey1],

     [Extent6].[GeographyKey] AS [GeographyKey1],

     [Extent6].[StoreManager] AS [StoreManager],

     [Extent6].[StoreType] AS [StoreType],

     [Extent6].[StoreName] AS [StoreName],

     [Extent6].[StoreDescription] AS [StoreDescription],

     [Extent6].[Status] AS [Status1],

     [Extent6].[OpenDate] AS [OpenDate],

     [Extent6].[CloseDate] AS [CloseDate],

     [Extent6].[EntityKey] AS [EntityKey],

     [Extent6].[ZipCode] AS [ZipCode],

     [Extent6].[ZipCodeExtension] AS [ZipCodeExtension],

     [Extent6].[StorePhone] AS [StorePhone],

     [Extent6].[StoreFax] AS [StoreFax],

     [Extent6].[AddressLine1] AS [AddressLine11],

     [Extent6].[AddressLine2] AS [AddressLine21],

     [Extent6].[CloseReason] AS [CloseReason],

     [Extent6].[EmployeeCount] AS [EmployeeCount],

     [Extent6].[SellingAreaSize] AS [SellingAreaSize],

     [Extent6].[LastRemodelDate] AS [LastRemodelDate],

     [Extent6].[ETLLoadID] AS [ETLLoadID5],

     [Extent6].[LoadDate] AS [LoadDate5],

     [Extent6].[UpdateDate] AS [UpdateDate5],

     CASE WHEN ([Extent2].[OnlineSalesKey] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]

     FROM (SELECT TOP (50) [c].[CustomerKey] AS [CustomerKey], [c].[GeographyKey] AS [GeographyKey], [c].[CustomerLabel] AS [CustomerLabel], [c].[Title] AS [Title], [c].[FirstName] AS [FirstName], [c].[MiddleName] AS [MiddleName], [c].[LastName] AS [LastName], [c].[NameStyle] AS [NameStyle], [c].[BirthDate] AS [BirthDate], [c].[MaritalStatus] AS [MaritalStatus], [c].[Suffix] AS [Suffix], [c].[Gender] AS [Gender], [c].[EmailAddress] AS [EmailAddress], [c].[YearlyIncome] AS [YearlyIncome], [c].[TotalChildren] AS [TotalChildren], [c].[NumberChildrenAtHome] AS [NumberChildrenAtHome], [c].[Education] AS [Education], [c].[Occupation] AS [Occupation], [c].[HouseOwnerFlag] AS [HouseOwnerFlag], [c].[NumberCarsOwned] AS [NumberCarsOwned], [c].[AddressLine1] AS [AddressLine1], [c].[AddressLine2] AS [AddressLine2], [c].[Phone] AS [Phone], [c].[DateFirstPurchase] AS [DateFirstPurchase], [c].[CustomerType] AS [CustomerType], [c].[CompanyName] AS [CompanyName], [c].[ETLLoadID] AS [ETLLoadID], [c].[LoadDate] AS [LoadDate], [c].[UpdateDate] AS [UpdateDate]

         FROM [dbo].[DimCustomer] AS [c] ) AS [Limit1]

     LEFT OUTER JOIN [dbo].[FactOnlineSales] AS [Extent2] ON [Limit1].[CustomerKey] = [Extent2].[CustomerKey]

     LEFT OUTER JOIN [dbo].[DimProduct] AS [Extent3] ON [Extent2].[ProductKey] = [Extent3].[ProductKey]

     LEFT OUTER JOIN [dbo].[DimProductSubcategory] AS [Extent4] ON [Extent3].[ProductSubcategoryKey] = [Extent4].[ProductSubcategoryKey]

     LEFT OUTER JOIN [dbo].[DimProductCategory] AS [Extent5] ON [Extent4].[ProductCategoryKey] = [Extent5].[ProductCategoryKey]

     LEFT OUTER JOIN [dbo].[DimStore] AS [Extent6] ON [Extent2].[StoreKey] = [Extent6].[StoreKey]

ORDER BY [Limit1].[CustomerKey] ASC, [C1] ASC

These queries return 8338 rows.

 

Now you will take a look on the bytes received from server. With these queries, it is 13 082 440 so more than 13 MB that will transit from the DB to your application or your server.

 

Why so much?

Because we will have many identic information. We get 8338 rows for only 50 customers with 121 columns.

 

Another point here is the fact that EF need much time to generated SQL and to materialize entities from datarows.

 

Now we will see what happens if we load the graph in several queries. Not thousands of queries like you will have with lazy loading (which is catastrophic for performance) but with 6 SQL queries: one per EntitySets.

 

var customers = context.Customers.Take(50).ToList();
 
foreach (var onlineSale in context.Customers.Take(50).SelectMany(c => c.OnlineSales));
 
foreach (var product in context.Products.Where(p => context.Customers.Take(50).SelectMany(c => c.OnlineSales).Any(os => os.ProductKey == p.ProductKey)));
 
foreach(var subcategory in context.ProductSubcategories.Where(sc => context.Customers.Take(50).SelectMany(c => c.OnlineSales).Any(os => os.Product.ProductSubcategoryKey == sc.ProductSubcategoryKey)));
 
foreach (var category in context.ProductCategories.Where(ca => context.Customers.Take(50).SelectMany(c => c.OnlineSales).Any(os => os.Product.ProductSubcategory.ProductCategoryKey == ca.ProductCategoryKey)));
 
foreach (var store in context.Stores.Where(s => context.Customers.Take(50).SelectMany(c => c.OnlineSales).Any(os => os.StoreKey == s.StoreKey)));

 

Note that the foreach (); is useful because EF will instantiate entities when it will iterate on them. Then, the ObjectContext will attach itself entities together (ie fill navigation properties) using FK.

Now we will look at the SQL for it:

SELECT TOP (50)

[c].[CustomerKey] AS [CustomerKey],

[c].[GeographyKey] AS [GeographyKey],

[c].[CustomerLabel] AS [CustomerLabel],

[c].[Title] AS [Title],

[c].[FirstName] AS [FirstName],

[c].[MiddleName] AS [MiddleName],

[c].[LastName] AS [LastName],

[c].[NameStyle] AS [NameStyle],

[c].[BirthDate] AS [BirthDate],

[c].[MaritalStatus] AS [MaritalStatus],

[c].[Suffix] AS [Suffix],

[c].[Gender] AS [Gender],

[c].[EmailAddress] AS [EmailAddress],

[c].[YearlyIncome] AS [YearlyIncome],

[c].[TotalChildren] AS [TotalChildren],

[c].[NumberChildrenAtHome] AS [NumberChildrenAtHome],

[c].[Education] AS [Education],

[c].[Occupation] AS [Occupation],

[c].[HouseOwnerFlag] AS [HouseOwnerFlag],

[c].[NumberCarsOwned] AS [NumberCarsOwned],

[c].[AddressLine1] AS [AddressLine1],

[c].[AddressLine2] AS [AddressLine2],

[c].[Phone] AS [Phone],

[c].[DateFirstPurchase] AS [DateFirstPurchase],

[c].[CustomerType] AS [CustomerType],

[c].[CompanyName] AS [CompanyName],

[c].[ETLLoadID] AS [ETLLoadID],

[c].[LoadDate] AS [LoadDate],

[c].[UpdateDate] AS [UpdateDate]

FROM [dbo].[DimCustomer] AS [c]

 

SELECT

[Extent2].[OnlineSalesKey] AS [OnlineSalesKey],

[Extent2].[DateKey] AS [DateKey],

[Extent2].[StoreKey] AS [StoreKey],

[Extent2].[ProductKey] AS [ProductKey],

[Extent2].[PromotionKey] AS [PromotionKey],

[Extent2].[CurrencyKey] AS [CurrencyKey],

[Extent2].[CustomerKey] AS [CustomerKey],

[Extent2].[SalesOrderNumber] AS [SalesOrderNumber],

[Extent2].[SalesOrderLineNumber] AS [SalesOrderLineNumber],

[Extent2].[SalesQuantity] AS [SalesQuantity],

[Extent2].[SalesAmount] AS [SalesAmount],

[Extent2].[ReturnQuantity] AS [ReturnQuantity],

[Extent2].[ReturnAmount] AS [ReturnAmount],

[Extent2].[DiscountQuantity] AS [DiscountQuantity],

[Extent2].[DiscountAmount] AS [DiscountAmount],

[Extent2].[TotalCost] AS [TotalCost],

[Extent2].[UnitCost] AS [UnitCost],

[Extent2].[UnitPrice] AS [UnitPrice],

[Extent2].[ETLLoadID] AS [ETLLoadID],

[Extent2].[LoadDate] AS [LoadDate],

[Extent2].[UpdateDate] AS [UpdateDate]

FROM   (SELECT TOP (50) [c].[CustomerKey] AS [CustomerKey]

      FROM [dbo].[DimCustomer] AS [c] ) AS [Limit1]

INNER JOIN [dbo].[FactOnlineSales] AS [Extent2] ON [Limit1].[CustomerKey] = [Extent2].[CustomerKey]

 

SELECT

[Extent1].[ProductKey] AS [ProductKey],

[Extent1].[ProductLabel] AS [ProductLabel],

[Extent1].[ProductName] AS [ProductName],

[Extent1].[ProductDescription] AS [ProductDescription],

[Extent1].[ProductSubcategoryKey] AS [ProductSubcategoryKey],

[Extent1].[Manufacturer] AS [Manufacturer],

[Extent1].[BrandName] AS [BrandName],

[Extent1].[ClassID] AS [ClassID],

[Extent1].[ClassName] AS [ClassName],

[Extent1].[StyleID] AS [StyleID],

[Extent1].[StyleName] AS [StyleName],

[Extent1].[ColorID] AS [ColorID],

[Extent1].[ColorName] AS [ColorName],

[Extent1].[Size] AS [Size],

[Extent1].[SizeRange] AS [SizeRange],

[Extent1].[SizeUnitMeasureID] AS [SizeUnitMeasureID],

[Extent1].[Weight] AS [Weight],

[Extent1].[WeightUnitMeasureID] AS [WeightUnitMeasureID],

[Extent1].[UnitOfMeasureID] AS [UnitOfMeasureID],

[Extent1].[UnitOfMeasureName] AS [UnitOfMeasureName],

[Extent1].[StockTypeID] AS [StockTypeID],

[Extent1].[StockTypeName] AS [StockTypeName],

[Extent1].[UnitCost] AS [UnitCost],

[Extent1].[UnitPrice] AS [UnitPrice],

[Extent1].[AvailableForSaleDate] AS [AvailableForSaleDate],

[Extent1].[StopSaleDate] AS [StopSaleDate],

[Extent1].[Status] AS [Status],

[Extent1].[ImageURL] AS [ImageURL],

[Extent1].[ProductURL] AS [ProductURL],

[Extent1].[ETLLoadID] AS [ETLLoadID],

[Extent1].[LoadDate] AS [LoadDate],

[Extent1].[UpdateDate] AS [UpdateDate]

FROM [dbo].[DimProduct] AS [Extent1]

WHERE  EXISTS (SELECT

      1 AS [C1]

      FROM   (SELECT TOP (50) [c].[CustomerKey] AS [CustomerKey]

            FROM [dbo].[DimCustomer] AS [c] ) AS [Limit1]

      INNER JOIN [dbo].[FactOnlineSales] AS [Extent3] ON [Limit1].[CustomerKey] = [Extent3].[CustomerKey]

      WHERE [Extent3].[ProductKey] = [Extent1].[ProductKey]

)

 

SELECT

[Extent1].[ProductSubcategoryKey] AS [ProductSubcategoryKey],

[Extent1].[ProductSubcategoryLabel] AS [ProductSubcategoryLabel],

[Extent1].[ProductSubcategoryName] AS [ProductSubcategoryName],

[Extent1].[ProductSubcategoryDescription] AS [ProductSubcategoryDescription],

[Extent1].[ProductCategoryKey] AS [ProductCategoryKey],

[Extent1].[ETLLoadID] AS [ETLLoadID],

[Extent1].[LoadDate] AS [LoadDate],

[Extent1].[UpdateDate] AS [UpdateDate]

FROM [dbo].[DimProductSubcategory] AS [Extent1]

WHERE  EXISTS (SELECT

      1 AS [C1]

      FROM    (SELECT TOP (50) [c].[CustomerKey] AS [CustomerKey]

            FROM [dbo].[DimCustomer] AS [c] ) AS [Limit1]

      INNER JOIN [dbo].[FactOnlineSales] AS [Extent3] ON [Limit1].[CustomerKey] = [Extent3].[CustomerKey]

      INNER JOIN [dbo].[DimProduct] AS [Extent4] ON [Extent3].[ProductKey] = [Extent4].[ProductKey]

      WHERE [Extent4].[ProductSubcategoryKey] = [Extent1].[ProductSubcategoryKey]

)

 

SELECT

[Extent1].[ProductCategoryKey] AS [ProductCategoryKey],

[Extent1].[ProductCategoryLabel] AS [ProductCategoryLabel],

[Extent1].[ProductCategoryName] AS [ProductCategoryName],

[Extent1].[ProductCategoryDescription] AS [ProductCategoryDescription],

[Extent1].[ETLLoadID] AS [ETLLoadID],

[Extent1].[LoadDate] AS [LoadDate],

[Extent1].[UpdateDate] AS [UpdateDate]

FROM [dbo].[DimProductCategory] AS [Extent1]

WHERE  EXISTS (SELECT

      1 AS [C1]

      FROM     (SELECT TOP (50) [c].[CustomerKey] AS [CustomerKey]

            FROM [dbo].[DimCustomer] AS [c] ) AS [Limit1]

      INNER JOIN [dbo].[FactOnlineSales] AS [Extent3] ON [Limit1].[CustomerKey] = [Extent3].[CustomerKey]

      INNER JOIN [dbo].[DimProduct] AS [Extent4] ON [Extent3].[ProductKey] = [Extent4].[ProductKey]

      INNER JOIN [dbo].[DimProductSubcategory] AS [Extent5] ON [Extent4].[ProductSubcategoryKey] = [Extent5].[ProductSubcategoryKey]

      WHERE [Extent5].[ProductCategoryKey] = [Extent1].[ProductCategoryKey]

)

 

SELECT

[Extent1].[StoreKey] AS [StoreKey],

[Extent1].[GeographyKey] AS [GeographyKey],

[Extent1].[StoreManager] AS [StoreManager],

[Extent1].[StoreType] AS [StoreType],

[Extent1].[StoreName] AS [StoreName],

[Extent1].[StoreDescription] AS [StoreDescription],

[Extent1].[Status] AS [Status],

[Extent1].[OpenDate] AS [OpenDate],

[Extent1].[CloseDate] AS [CloseDate],

[Extent1].[EntityKey] AS [EntityKey],

[Extent1].[ZipCode] AS [ZipCode],

[Extent1].[ZipCodeExtension] AS [ZipCodeExtension],

[Extent1].[StorePhone] AS [StorePhone],

[Extent1].[StoreFax] AS [StoreFax],

[Extent1].[AddressLine1] AS [AddressLine1],

[Extent1].[AddressLine2] AS [AddressLine2],

[Extent1].[CloseReason] AS [CloseReason],

[Extent1].[EmployeeCount] AS [EmployeeCount],

[Extent1].[SellingAreaSize] AS [SellingAreaSize],

[Extent1].[LastRemodelDate] AS [LastRemodelDate],

[Extent1].[ETLLoadID] AS [ETLLoadID],

[Extent1].[LoadDate] AS [LoadDate],

[Extent1].[UpdateDate] AS [UpdateDate]

FROM [dbo].[DimStore] AS [Extent1]

WHERE  EXISTS (SELECT

      1 AS [C1]

      FROM   (SELECT TOP (50) [c].[CustomerKey] AS [CustomerKey]

            FROM [dbo].[DimCustomer] AS [c] ) AS [Limit1]

      INNER JOIN [dbo].[FactOnlineSales] AS [Extent3] ON [Limit1].[CustomerKey] = [Extent3].[CustomerKey]

      WHERE [Extent3].[StoreKey] = [Extent1].[StoreKey]
)

What about SQL performance?

The client processing time is 4672ms to execute which is very good comparing to the SQL generated by EF and almost the same that the one I previously wrote.

The good point with this way is the fact that you don’t have duplicated information on datarows. So we reduced a lot the number of bytes received from server. With these 6 queries, it is 1 302 789 so 10 less than previously!

However, it’s a shame to execute these queries synchronously. It would be great to execute them in different threads but sadly, the ObjectContext is not thread safe.

So my idea is to run them in different ObjectContext and then to attach them in the same ObjectContext.

List<Customer> customers;
object lockObject = new object();
 
Task customersTask = new Task(() =>
    {
        using (var context2 = new ContosoRetailDWEntities())
        {
            var query = context2.Customers.Take(50);
            var objectQuery = (ObjectQuery<Customer>)query;
            objectQuery.MergeOption = MergeOption.NoTracking;
            customers = objectQuery.ToList();
            lock (lockObject)
            {
                foreach (var c in customers)
                    context.Customers.Attach(c);
            }
        }
    });
customersTask.Start();
Task onlineSalesTask = new Task(() =>
    {
        using (var context2 = new ContosoRetailDWEntities())
        {
            var query = context2.Customers.Take(50).SelectMany(c => c.OnlineSales).Take(50);
            var objectQuery = (ObjectQuery<OnlineSale>)query;
            objectQuery.MergeOption = MergeOption.NoTracking;
            var onlineSales = objectQuery.ToList();
            lock (lockObject)
            {
                foreach (var os in onlineSales)
                    context.OnlineSales.Attach(os);
            }
        }
    });
onlineSalesTask.Start();
Task productsTask = new Task(() =>
    {
        using (var context2 = new ContosoRetailDWEntities())
        {
            var query = context2.Products.Where(p => context2.Customers.Take(50).SelectMany(c => c.OnlineSales).Any(os => os.ProductKey == p.ProductKey));
            var objectQuery = (ObjectQuery<Product>)query;
            objectQuery.MergeOption = MergeOption.NoTracking;
            var products = objectQuery.ToList();
            lock (lockObject)
            {
                foreach (var p in products)
                    context.Products.Attach(p);
            }
        }
    });
productsTask.Start();
Task subCategoriesTask = new Task(() =>
{
    using (var context2 = new ContosoRetailDWEntities())
    {
        var query = context2.ProductSubcategories.Where(sc => context2.Customers.Take(50).SelectMany(c => c.OnlineSales).Any(os => os.Product.ProductSubcategoryKey == sc.ProductSubcategoryKey));
        var objectQuery = (ObjectQuery<ProductSubcategory>)query;
        objectQuery.MergeOption = MergeOption.NoTracking;
        var subCategories = objectQuery.ToList();
        lock (lockObject)
        {
            foreach (var sc in subCategories)
                context.ProductSubcategories.Attach(sc);
        }
    }
});
subCategoriesTask.Start();
Task categoriesTask = new Task(() =>
{
    using (var context2 = new ContosoRetailDWEntities())
    {
        var query = context2.ProductCategories.Where(ca => context2.Customers.Take(50).SelectMany(c => c.OnlineSales).Any(os => os.Product.ProductSubcategory.ProductCategoryKey == ca.ProductCategoryKey));
        var objectQuery = (ObjectQuery<ProductCategory>)query;
        objectQuery.MergeOption = MergeOption.NoTracking;
        var categories = objectQuery.ToList();
        lock (lockObject)
        {
            foreach (var c in categories)
                context.ProductCategories.Attach(c);
        }
    }
});
categoriesTask.Start();
Task storesTask = new Task(() =>
{
    using (var context2 = new ContosoRetailDWEntities())
    {
        var query = context2.Stores.Where(s => context2.Customers.Take(50).SelectMany(c => c.OnlineSales).Any(os => os.StoreKey == s.StoreKey));
        var objectQuery = (ObjectQuery<Store>)query;
        objectQuery.MergeOption = MergeOption.NoTracking;
        var stores = objectQuery.ToList();
        lock (lockObject)
        {
            foreach (var s in stores)
                context.Stores.Attach(s);
        }
    }
});
storesTask.Start();
 
Task.WaitAll(customersTask, onlineSalesTask, productsTask, subCategoriesTask, categoriesTask, storesTask);

Like this, if we have a enough good DB Server, all the queries will be executed on parallel so you only have to wait the time of the longest SQL query: 1 214 ms.

 

Now there is three other points to take in consideration: the time that EF needs to generate the query, the time for EF to materialize datarows to entities and the attachment logic.

In the two first points my way is better than with Include method, particularly on materialization which is difficult when you have the properties of many entities mapped to columns of the same datarow.

For the attachment logic, I use a shortcut. Indeed, I should first check if the ObjectContext does not already attach an entity with the same key before attaching one. However, even like this, this solution is significantly faster than the one with Include method.

Roslyn: InvokeActivity using T4

Imagine the following scenario: in a project, there are developers and some functional experts. Functional experts have to design some workflows. Developers have to code some methods usable by workflows.

The issue in this scenario is InvokeActivity. To use it, they have to know the method signature to specify the good parameters / result type. Another issue is the fact that they have to write themselves the method name (with potential typo).

They would probably prefer to drag and drop some activities in the workflow.

So my idea is to encapsulate InvokeActivity into an Activity. Now it could be very bored for the developer to create all of these activities.

For this, developers can decorate methods using Attribute and they can use T4 to generate activities.

T4 metacode can use Roslyn in order to get method information.

I include the EF.Utility.CS.ttinclude that allows creating many files from a T4 (in my case, I want one file per method).

<#@ include file="EF.Utility.CS.ttinclude"#>

I add Roslyn assemblies in my T4:

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Compilers.dll"#>

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Compilers.CSharp.dll"#>

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Services.dll"#>

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Services.CSharp.dll"#>

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Services.VisualBasic.dll"#>

The solutionPath and the projectAssemblyName are specified at the beginning of the T4. So now, I can load the solution using Roslyn and get methods decorated with my Attribute in the specified project.

var solution = Solution.Load(solutionPath);

var project = solution.Projects.First(p => p.AssemblyName == projectAssemblyName);

foreach (var document in project.Documents)

{

   //…

}

Then, to identify methods, I use a SyntaxVisitor:

public class InvokeActivityAttributeVisitor : SyntaxVisitor<object>

{

    protected override object VisitCompilationUnit(CompilationUnitSyntax node)

    {

        foreach (var n in node.ChildNodes())

            Visit(n);

        return null;

    }

 

    protected override object VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)

    {

        foreach (var n in node.ChildNodes())

            Visit(n);

        return null;

    }

 

    protected override object VisitTypeDeclaration(TypeDeclarationSyntax node)

    {

        foreach (var n in node.ChildNodes())

            Visit(n);

        return null;

    }

 

    protected override object VisitMethodDeclaration(MethodDeclarationSyntax node)

    {

        if (node.Modifiers.Any(st => st.Kind == SyntaxKind.PublicKeyword) && node.Attributes.Any(a => a.Attributes.Any(a2 => Regex.IsMatch(a2.Name.GetFullText(), @"^(Roslyn.WF.ActivityGenerator.)?InvokeActivity$"))))

        {

            string methodName = node.Identifier.GetText();
            var returnTypeAsPredefinedTypeSyntax = node.ReturnType as PredefinedTypeSyntax;
            if (returnTypeAsPredefinedTypeSyntax == null || returnTypeAsPredefinedTypeSyntax.Keyword.Kind != SyntaxKind.VoidKeyword)
            {
                //…       
            }
            foreach (var parameter in node.ParameterList.Parameters)
            {
                //…       
            }

        }

    }

}

Now the point is to know types namespace and assembly. For this, we will use compilation symbols:

_syntaxTree = (SyntaxTree)_document.GetSyntaxTree();
_semanticModel = _document.Project.GetCompilation().GetSemanticModel(_syntaxTree);
var returnTypeSymbol = _semanticModel.GetSemanticInfo(node.ReturnType).Symbol;
string returnTypeAssemblyName = returnTypeSymbol.ContainingAssembly.AssemblyName.Name;
string returnTypeNamespaceName = returnTypeSymbol.ContainingNamespace.ToString();

We also need to know if parameters are In/Out/InOut:

parameter.Modifiers.Any(st => st.Kind == SyntaxKind.RefKeyword) ? Direction.InOut : (parameter.Modifiers.Any(st => st.Kind == SyntaxKind.OutKeyword) ? Direction.Out : Direction.In)

The rest of the code is basic, used to generate WF Activities.

You can download a sample here.

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

How to add behaviors on ObjectContext?

Imagine that you want to add a behavior on your ObjectContext in order to catch some exceptions for example. If you try to delete an entity that is already deleted, you don’t want to have an exception. In the same idea, if you want to delete an entity that was updated, you don’t want to have an exception.

So how to do it with a reusable code?

A solution could be to have an ExceptionHandlingObjectContext class that inherits of ObjectContext and then you can, in your specific ObjectContext (ex: NorthwindEntities) inherits if ExceptionHandlingObjectContext instead of inheriting of ObjectContext.

However, we can imagine to also have a TracingObjectContext and in this case you perhaps want to use it or not by configuration using unity for example. So my previous solution is not good.

So, my idea is to use a different approach.

I will create a CustomizableObjectContext class which does not know its behavior. My specific ObjectContext will inherit of it and then, I will add behaviors on it using delegate.

public class CustomizableObjectContext : ObjectContext
{
public CustomizableObjectContext(EntityConnection
connection)
:
base
(connection)
{
}
public CustomizableObjectContext(string
connectionString)
:
base
(connectionString)
{
}
protected CustomizableObjectContext(EntityConnection connection, string
defaultContainerName)
:
base
(connection, defaultContainerName)
{
}
protected CustomizableObjectContext(string connectionString, string
defaultContainerName)
:
base
(connectionString, defaultContainerName)
{
}

private List<Func<ObjectContext, SaveOptions, Func<SaveOptions, int>, int
>> _saveActions =
new List<Func<ObjectContext, SaveOptions, Func<SaveOptions, int>,int
>>();

public void AddSaveAction(Func<ObjectContext, SaveOptions, Func<SaveOptions, int>, int
> saveAction)
{
_saveActions.Add(saveAction);
}

public override int SaveChanges(SaveOptions
options)
{
Func<int, SaveOptions, int> saveAction = null
;
saveAction = (index, saveOptions) =>
{
if
(index == -1)
return base
.SaveChanges(saveOptions);
return _saveActions[index](this
, saveOptions, so => saveAction(index - 1, so));
};
return saveAction(_saveActions.Count - 1, options);
}
}

Now I can define my ExceptionHandling behavior:

public abstract class ObjectContextCustomizerBase<T> 
where T : ObjectContextCustomizerBase<T>
{
protected ObjectContext ObjectContext { get; set; }

protected abstract int SaveChanges(ObjectContext context, SaveOptions options, Func<SaveOptions, int> baseSaveChanges);
}
public abstract class ObjectContextCustomizer<T>  : ObjectContextCustomizerBase<T>
where T : ObjectContextCustomizer<T>, new()
{
public static OC CreateObjectContext<OC>(OC objectContext)
where OC : CustomizableObjectContext
{
objectContext.AddSaveAction(new T { ObjectContext = objectContext }.SaveChanges);
return objectContext;
}
}
public class ExceptionHandlerObjectContext : ObjectContextCustomizer<ExceptionHandlerObjectContext>
{
protected override int SaveChanges(ObjectContext context, SaveOptions options, Func<SaveOptions, int
> baseSaveChanges)
{
int
value = 0;
try
{
value = baseSaveChanges(options);
}
catch (OptimisticConcurrencyException
e)
{
bool canHandle = true
;
if
(e.StateEntries.Any() && e.StateEntries.Select(se =>
{
if (se.State != System.Data.EntityState
.Deleted)
return false
;
object
dbEntity;
if (ObjectContext.TryGetObjectByKey(se.EntityKey, out
dbEntity))
ObjectContext.ApplyOriginalValues(se.EntitySet.Name, dbEntity);
else
ObjectContext.Detach(se.Entity);
return true
;
}).TakeWhile(b => canHandle).Aggregate((b1, b2) => canHandle = b2))
// Aggregate for execute select on each of them
try
{
value = baseSaveChanges(options);
}
catch (System.Exception
e2)
{
throw e2;
}
else
throw e2;
}
return value;
}
}

Here we are. Now, to use it with Unity, we can use this code:

unityContainer.RegisterType<NorthwindEntities, NorthwindEntities>(new InjectionFactory(c => ExceptionHandlerObjectContext.CreateObjectContext(new NorthwindEntities())));
Posted by Matthieu MEZIL | with no comments

SL5: ICustomTypeProvider

We often have the following issue with SL4: in the Model we get a collection of entities and in the ViewModel, we want a collection of these entities but with VM intelligence (for example some calculated properties).

In this case the issue is to manage the two collections and we have to propagate modifications in two ways. An idea is to use a “RelayCollection” which does the job.

In .NET, we can add “fake” properties used for binding by implementing ICustomTypeDescriptor but we don’t have it in SL.

However, with SL5, we have a really great solution using ICustomTypeProvider interface.

I based my code on Alexandra one and I tried to improve it a little bit.

So this is my code:

public abstract class DynamicBaseType
{
   
public abstract object GetPropertyValue(string
propertyName);
   
public abstract void SetPropertyValue(string propertyName, object
value);
}
public abstract class DynamicBaseType<T> : DynamicBaseType, ICustomTypeProvider, INotifyPropertyChanged
    where T : DynamicBaseType
<T> {
   
private static List<CustomPropertyInfo> _customProperties = new List<CustomPropertyInfo
>();
   
private Dictionary<string, object
> _customPropertyValues;
   
private CustomType
_customtype;

   
protected
DynamicBaseType()
    {
        _customPropertyValues =
new Dictionary<string, object
>();
       
foreach (var property in
_customProperties)
            _customPropertyValues.Add(property.Name,
null
);
    }

   
public static void AddProperty(string name, Type type, object value = null, List<Attribute> attributes = null
)
    {
       
if
(!CheckIfNameExists(name))
            _customProperties.Add(
new CustomPropertyInfo
(name, type, value, attributes));
    }

   
public static void AddProperty<V>(string name, Func<T, V> get, Action<T, V> set = null, List<Attribute> attributes = null, string[] properties = null
)
    {
       
if
(!CheckIfNameExists(name))
            _customProperties.Add(
new CustomPropertyInfo
<V>(name, get, set, attributes, properties));
    }

   
private static bool CheckIfNameExists(string
name)
    {
       
if (_customProperties.Select(p => p.Name).Contains(name) || typeof
(T).GetProperties().Select(p => p.Name).Contains(name))
           
throw new Exception("The property with this name already exists: "
+ name);
       
return false
;
    }

   
private bool ValidateValueType(object value, Type
type)
    {
       
if (value == null
)
        {
           
if
(!type.IsValueType)
               
return true
;
           
return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable
<>));
        }
       
return
type.IsAssignableFrom(value.GetType());
    }

   
public override object GetPropertyValue(string
propertyName)
    {
       
object
customPropertyValue;
       
if (_customPropertyValues.TryGetValue(propertyName, out
customPropertyValue))
           
return customPropertyValue ?? _customProperties.First(p => p.Name == propertyName).GetDefaultValue(this
);
       
throw new Exception("There is no property "
+ propertyName);
    }

   
public override void SetPropertyValue(string propertyName, object
value)
    {
       
CustomPropertyInfo
propertyInfo = _customProperties.FirstOrDefault(prop => prop.Name == propertyName);
       
object
customPropertyValue;
       
if (!_customPropertyValues.TryGetValue(propertyName, out
customPropertyValue))
           
throw new Exception("There is no property "
+ propertyName);
       
if
(ValidateValueType(value, propertyInfo.PropertyType))
        {
           
if
(customPropertyValue != value)
            {
                _customPropertyValues[propertyName] = value;
                OnPropertyChanged(propertyName);
            }
        }
       
else throw new Exception("Value is of the wrong type or null for a non-nullable type."
);
    }

   
public PropertyInfo
[] GetProperties()
    {
       
return this
.GetCustomType().GetProperties();
    }

   
public Type
GetCustomType()
    {
       
return _customtype ?? (_customtype = new CustomType(typeof
(T)));
    }

   
protected virtual void OnPropertyChanged(string
propertyName)
    {
       
if (PropertyChanged != null
)
        {
            PropertyChanged(
this, new PropertyChangedEventArgs
(propertyName));
           
foreach (var dependantCustomPropertyInfo in _customProperties.OfType<IDependantCustomPropertyInfo
>().Where(dcpi => dcpi.Properties.Contains(propertyName)))
                PropertyChanged(
this, new PropertyChangedEventArgs
(dependantCustomPropertyInfo.Name));
        }
    }
   
public event PropertyChangedEventHandler
PropertyChanged;

   
private class CustomType : Type
    {
       
Type
_baseType;
       
public CustomType(Type
delegatingType)
        {
            _baseType = delegatingType;
        }
       
public override Assembly
Assembly
        {
           
get { return
_baseType.Assembly; }
        }

       
public override string
AssemblyQualifiedName
        {
           
get { return
_baseType.AssemblyQualifiedName; }
        }

       
public override Type
BaseType
        {
           
get { return
_baseType.BaseType; }
        }

       
public override string
FullName
        {
           
get { return
_baseType.FullName; }
        }

       
public override Guid
GUID
        {
           
get { return
_baseType.GUID; }
        }

       
protected override TypeAttributes
GetAttributeFlagsImpl()
        {
           
throw new NotImplementedException
();
        }

       
protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier
[] modifiers)
        {
           
throw new NotImplementedException
();
        }

       
public override ConstructorInfo[] GetConstructors(BindingFlags
bindingAttr)
        {
           
return
_baseType.GetConstructors(bindingAttr);
        }

       
public override Type
GetElementType()
        {
           
return
_baseType.GetElementType();
        }

       
public override EventInfo GetEvent(string name, BindingFlags
bindingAttr)
        {
           
return
_baseType.GetEvent(name, bindingAttr);
        }

       
public override EventInfo[] GetEvents(BindingFlags
bindingAttr)
        {
           
return
_baseType.GetEvents(bindingAttr);
        }

       
public override FieldInfo GetField(string name, BindingFlags
bindingAttr)
        {
           
return
_baseType.GetField(name, bindingAttr);
        }

       
public override FieldInfo[] GetFields(BindingFlags
bindingAttr)
        {
           
return
_baseType.GetFields(bindingAttr);
        }

       
public override Type GetInterface(string name, bool
ignoreCase)
        {
           
return
_baseType.GetInterface(name, ignoreCase);
        }

       
public override Type
[] GetInterfaces()
        {
           
return
_baseType.GetInterfaces();
        }

       
public override MemberInfo[] GetMembers(BindingFlags
bindingAttr)
        {
           
return
_baseType.GetMembers(bindingAttr);
        }

       
protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier
[] modifiers)
        {
           
throw new NotImplementedException
();
        }

       
public override MethodInfo[] GetMethods(BindingFlags
bindingAttr)
        {
           
return
_baseType.GetMethods(bindingAttr);
        }

       
public override Type GetNestedType(string name, BindingFlags
bindingAttr)
        {
           
return
_baseType.GetNestedType(name, bindingAttr);
        }

       
public override Type[] GetNestedTypes(BindingFlags
bindingAttr)
        {
           
return
_baseType.GetNestedTypes(bindingAttr);
        }

       
public override PropertyInfo[] GetProperties(BindingFlags
bindingAttr)
        {
           
PropertyInfo
[] clrProperties = _baseType.GetProperties(bindingAttr);
           
if (clrProperties != null
)
               
return
clrProperties.Concat(_customProperties).ToArray();
           
return
_customProperties.ToArray();
        }

       
protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier
[] modifiers)
        {
           
return
GetProperties(bindingAttr).FirstOrDefault(prop => prop.Name == name) ?? _customProperties.FirstOrDefault(prop => prop.Name == name);
        }

       
protected override bool
HasElementTypeImpl()
        {
           
throw new NotImplementedException
();
        }

       
public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string
[] namedParameters)
        {
           
return
_baseType.InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters);
        }

       
protected override bool
IsArrayImpl()
        {
           
throw new NotImplementedException
();
        }

       
protected override bool
IsByRefImpl()
        {
           
throw new NotImplementedException
();
        }

       
protected override bool
IsCOMObjectImpl()
        {
           
throw new NotImplementedException
();
        }

       
protected override bool
IsPointerImpl()
        {
           
throw new NotImplementedException
();
        }

       
protected override bool
IsPrimitiveImpl()
        {
           
return
_baseType.IsPrimitive;
        }

       
public override Module
Module
        {
           
get { return
_baseType.Module; }
        }

       
public override string
Namespace
        {
           
get { return
_baseType.Namespace; }
        }

       
public override Type
UnderlyingSystemType
        {
           
get { return
_baseType.UnderlyingSystemType; }
        }

       
public override object[] GetCustomAttributes(Type attributeType, bool
inherit)
        {
           
return
_baseType.GetCustomAttributes(attributeType, inherit);
        }

       
public override object[] GetCustomAttributes(bool
inherit)
        {
           
return
_baseType.GetCustomAttributes(inherit);
        }

       
public override bool IsDefined(Type attributeType, bool
inherit)
        {
           
return
_baseType.IsDefined(attributeType, inherit);
        }

       
public override string
Name
        {
           
get { return
_baseType.Name; }
        }
    }

   
private class CustomPropertyInfo : PropertyInfo
    {
       
private string
_name;
       
private Type
_type;
       
private object
_defaultValue;
       
private List<Attribute
> _attributes;

       
public CustomPropertyInfo(string name, Type type, object defaultValue = null, List<Attribute> attributes = null
)
            :
this
(name, type, attributes)
        {
            _defaultValue = defaultValue;
        }

       
protected CustomPropertyInfo(string name, Type type, List<Attribute> attributes = null
)
        {
            _name = name;
            _type = type;
            _attributes = attributes;
        }

       
public virtual object GetDefaultValue(DynamicBaseType
entity)
        {
           
return
_defaultValue;
        }

       
public override PropertyAttributes
Attributes
        {
           
get { throw new NotImplementedException
(); }
        }

       
public override bool
CanRead
        {
           
get { return true
; }
        }

       
public override bool
CanWrite
        {
           
get { return true
; }
        }

       
public override MethodInfo[] GetAccessors(bool
nonPublic)
        {
           
throw new NotImplementedException
();
        }

       
public override MethodInfo GetGetMethod(bool
nonPublic)
        {
           
throw new NotImplementedException
();
        }

       
public override ParameterInfo
[] GetIndexParameters()
        {
           
throw new NotImplementedException
();
        }

       
public override MethodInfo GetSetMethod(bool
nonPublic)
        {
           
throw new NotImplementedException
();
        }

       
public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo
culture)
        {
           
return ((DynamicBaseType
)obj).GetPropertyValue(_name);
        }

       
public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo
culture)
        {
            ((
DynamicBaseType
)obj).SetPropertyValue(_name, value);
        }

       
public override Type
PropertyType
        {
           
get { return
_type; }
        }

       
public override Type
DeclaringType
        {
           
get { throw new NotImplementedException
(); }
        }

       
public override object[] GetCustomAttributes(Type attributeType, bool
inherit)
        {
           
return _attributes == null ? new object
[0] : _attributes.Where(a => a.GetType() == attributeType).ToArray();
        }

       
public override object[] GetCustomAttributes(bool
inherit)
        {
           
return _attributes == null ? new object
[0] : _attributes.ToArray();
        }

       
public override bool IsDefined(Type attributeType, bool
inherit)
        {
           
throw new NotImplementedException
();
        }

       
public override string
Name
        {
           
get { return
_name; }
        }

       
public override Type
ReflectedType
        {
           
get { throw new NotImplementedException
(); }
        }
    }

   
private interface IDependantCustomPropertyInfo
    {
       
string Name { get
; }
       
string[] Properties { get
; }
    }

   
private class CustomPropertyInfo<V> : CustomPropertyInfo, IDependantCustomPropertyInfo
    {
       
private Func
<T, V> _get;
       
private Action
<T, V> _set;
       
private string
[] _properties;

       
public CustomPropertyInfo(string name, Func<T, V> get, Action<T, V> set = null, List<Attribute> attributes = null, string[] properties = null
)
            :
base(name, typeof
(V), attributes)
        {
            _get = get;
            _set = set;
            _properties = properties;
        }

       
public string
[] Properties
        {
           
get { return
_properties; }
        }

       
public override object GetDefaultValue(DynamicBaseType
entity)
        {
           
return
_get((T)entity);
        }

       
public override bool
CanWrite
        {
           
get { return _set != null
; }
        }

       
public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo
culture)
        {
           
return
_get((T)obj);
        }

       
public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo
culture)
        {
           
if (_set == null
)
               
throw new InvalidOperationException();
            _set((T)obj, (V)value);
        }
    }
}

Now to use it it’s very easy.

This is my class Customer:

public class Customer : DynamicBaseType<Customer>
{
   
private String
firstName;
   
public String
FirstName
    {
       
get
{ return firstName; }
       
set
        {
            firstName =
value
;
            OnPropertyChanged(
"FirstName"
);
        }
    }

   
private String
lastName;
   
public String
LastName
    {
       
get
{ return lastName; }
       
set
        {
            lastName =
value
;
            OnPropertyChanged(
"LastName");
        }
    }
}

And I use it like this in my ViewModel:

Customer.AddProperty("Age", typeof(int));
Customer.AddProperty("Married", typeof(bool));
Customer.AddProperty("FullName", c => string.Format("{0} {1}", c.LastName, c.FirstName), properties:new string[] { "LastName", "FirstName" });
 
customers[0].SetPropertyValue("Age", 40);
customers[0].SetPropertyValue("Married", true);
 
customers[1].SetPropertyValue("Age", 45);
customers[1].SetPropertyValue("Married", true);

What is interesting here comparing to her solution is the fact that FullName is calculated from other properties. And if I change the LastName or the FirstName of a Customer, my Customer instance will also raise a PropertyChanged with FullName.

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

Using a repository from WF

Imagine that you use a repository like this:

public class FooRepository
{
    public void Add(FooEntity entity) { /*TODO*/ }
    public void Update(FooEntity entity) { /*TODO*/ }
    public void Delete(FooEntityentity) { /*TODO*/ }
    public void SaveChanges() { /*TODO*/ }
    public void Dispose() { /*TODO*/ }
}

Then you want to be able to use your repository on a workflow.

Now I have a WCF workflow service.

image

GetFooEntity’s receive returns an instance of FooEntity that I set on a variable fooEntity of type FooEntity.

Imagine that between the Receive and SendReply, I want to Add my fooEntity in my repository, save my repository and finally dispose of it.

I can do it instantiating (and initializing) a variable of type FooRepository and using InvokeMethod activity:

image

image

As you can see, it’s a pain to use this way.

So my idea is to define a RepositoryScope activity to simplify it.

First, I will create a IRepository interface and my FooRepository class will implement it:

public interface IRepository : IDisposable
{
    void Add(object entity);
    void Update(object entity);
    void Delete(object entity);
    void SaveChanges();
}
 
public class FooRepository : IRepository 
{
    public void Add(FooEntity entity) { /*TODO*/ }
    public void Update(FooEntity entity) { /*TODO*/ }
    public void Delete(FooEntity entity) { /*TODO*/ }
    public void SaveChanges() { /*TODO*/ }
    public void Dispose() { /*TODO*/ }
 
    void IRepository.Add(object entity)
    {
        Add((FooEntity)entity);
    }
    void IRepository.Update(object entity)
    {
        Update((FooEntity)entity);
    }
    void IRepository.Delete(object entity)
    {
        Delete((FooEntity)entity);
    }
}

Now, I will define my RepositoryScope activity that inherits NativeActivity.

In my RepositoryScope, I want to be able to add many entities. For it, I could have a property of type Activity and use a sequence on it but it’s a better idea to use a Collection

public class RepositoryScope<RepositoryType> : NativeActivity
    where RepositoryType : IRepository, new()
{
    private Collection<Activity> _activities;
    public Collection<Activity> Activities
    {
        get { return _activities ?? (_activities = new Collection<Activity>()); }
    }

    protected override void Execute(NativeActivityContext context)
    {
        // TODO
    }
}

 

Now the first point is: how can I set this collection on the designer? For this, I can define my own ActivityDesigner and add Designer Attribute in my RepositoryScope<RepositoryType>.

[Designer(typeof(RepositoryScopeDesigner))]
 
<sap:ActivityDesigner x:Class="MyNamespace.RepositoryScopeDesigner"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
 
    <sap:ActivityDesigner.Resources>
        <Style TargetType="sap:WorkflowItemsPresenter">
            <Setter Property="SpacerTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <Rectangle Height="1"
                                   Stroke="Gray"
                                   Width="50"
                                   Margin="0,10,0,10" />
                    </DataTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="ItemsPanel">
                <Setter.Value>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Vertical" />
                    </ItemsPanelTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </sap:ActivityDesigner.Resources>
 
    <Grid>
        <sap:WorkflowItemsPresenter Items="{Binding Path=ModelItem.Activities}"
                                      HintText="Insert Activities Here" />
    </Grid>
</sap:ActivityDesigner>

Then, I can now use it.

image

image

Now my ScopeRepository activity has to schedule these activities. The easiest way is to use a Sequence, to schedule it on Execute method and then to remove activities from metadata, to add them into the Sequence and to add the Sequence in the activity children:

[Designer(typeof(RepositoryScopeDesigner))]
public class RepositoryScope<RepositoryType> : NativeActivity
    where RepositoryType : IRepository, new()
{
    private Collection<Activity> _activities;
    public Collection<Activity> Activities
    {
        get { return _activities ?? (_activities = new Collection<Activity>()); }
    }
 
    private Sequence _scopeSequence;
    private Sequence ScopeSequence
    {
        get { return _scopeSequence ?? (_scopeSequence = new Sequence()); }
    }
 
    protected override void Execute(NativeActivityContext context)
    {
        context.ScheduleActivity(ScopeSequence);
    }
 
    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        base.CacheMetadata(metadata);
 
        metadata.SetChildrenCollection(null);
 
        foreach (Activity activity in Activities)
            ScopeSequence.Activities.Add(activity);
 
        metadata.AddChild(ScopeSequence);
    }
}

Note the metadata.SetChildrenCollection(null) that is mandatory to affect them to to sequence.

 

Now the idea is to use the scope to do more than a sequence. So the scope will instantiate the repository and will also dispose of it. I will use a Try / Finally block to dispose of my repository anyway.

[Designer(typeof(RepositoryScopeDesigner))]
public class RepositoryScope<RepositoryType> : NativeActivity
    where RepositoryType : IRepository, new()
{
    public OutArgument<RepositoryType> Repository { get; set; }
 
    private Collection<Activity> _activities;
    public Collection<Activity> Activities
    {
        get { return _activities ?? (_activities = new Collection<Activity>()); }
    }
 
    private Sequence _scopeSequence;
    private Sequence ScopeSequence
    {
        get { return _scopeSequence ?? (_scopeSequence = new Sequence()); }
    }
 
    private TryCatch _tryCatch;
    private TryCatch TryCatch
    {
        get { return _tryCatch ?? (_tryCatch = new TryCatch()); }
    }
 
    private Activity _finallyActivity;
    public Activity FinallyActivity
    {
        get { return _finallyActivity ?? (_finallyActivity = new DisposeActivity()); }
    }
 
    protected override void Execute(NativeActivityContext context)
    {
        var repo = new RepositoryType();
        Repository.Set(context, repo);
        context.ScheduleActivity(TryCatch);
    }
 
    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        metadata.AddChild(FinallyActivity);
        base.CacheMetadata(metadata);
 
        metadata.SetChildrenCollection(null);
 
        foreach (Activity activity in Activities)
            ScopeSequence.Activities.Add(activity);
 
        TryCatch.Try = ScopeSequence;
        TryCatch.Finally = FinallyActivity;
 
        metadata.AddChild(TryCatch);
    }
}

It sounds good. However, how can the DisposeActivity know the repository?

I could use InArgument<IRepository>, however, there is a better way with NativeActivity: using Properties. Indeed, Properties are shared between an Activity and its Descendants.

So I will use it:

[Designer(typeof(RepositoryScopeDesigner))]
public class RepositoryScope<RepositoryType> : NativeActivity
    where RepositoryType : IRepository, new()
{
    public OutArgument<RepositoryType> Repository { get; set; }
 
    private Collection<Activity> _activities;
    public Collection<Activity> Activities
    {
        get { return _activities ?? (_activities = new Collection<Activity>()); }
    }
 
    private Sequence _scopeSequence;
    private Sequence ScopeSequence
    {
        get { return _scopeSequence ?? (_scopeSequence = new Sequence()); }
    }
 
    private TryCatch _tryCatch;
    private TryCatch TryCatch
    {
        get { return _tryCatch ?? (_tryCatch = new TryCatch()); }
    }
 
    private DisposeActivity _disposeActivity;
    public Activity DisposeActivity
    {
        get { return _disposeActivity ?? (_disposeActivity = new DisposeActivity()); }
    }
 
    protected override void Execute(NativeActivityContext context)
    {
        var repo = new RepositoryType();
        Repository.Set(context, repo);
        context.Properties.Add("Repository", repo);
        context.ScheduleActivity(TryCatch);
    }
 
    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        base.CacheMetadata(metadata);
 
        metadata.SetChildrenCollection(null);
 
        foreach (Activity activity in Activities)
            ScopeSequence.Activities.Add(activity);
 
        TryCatch.Try = ScopeSequence;
        TryCatch.Finally = DisposeActivity;
 
        metadata.AddChild(TryCatch);
    }

}

 

internal class DisposeActivity : NativeActivity
{
    protected override void Execute(NativeActivityContext context)
    {
        ((IRepository)context.Properties.Find("Repository")).Dispose();
    }
}

Great! Now I don’t need to initialize my fooRepository variable I will set it with my Repository OutArgument. I don’t need the Dispose InvokeMethod (because it’s already call by my RepositoryScope).

image

 

image

 

Now what I want to improve is my repository usage.

First, I don’t want to specify the method to call as a string.

So I will create new activities AddEntity, UpdateEntity, DeleteEntity and SaveChanges and I will use them in my workflow.

public class AddEntity : NativeActivity
{
    [RequiredArgument]
    public InArgument<IRepository> Repository { get; set; }
 
    [RequiredArgument]
    public InArgument<object> Entity { get; set; }
 
    protected override void Execute(NativeActivityContext context)
    {
        Repository.Get(context).Add(Entity.Get(context));
    }
}
 
public class SaveChanges : NativeActivity
{
    [RequiredArgument]
    public InArgument<IRepository> Repository { get; set; }
 
    protected override void Execute(NativeActivityContext context)
    {
        Repository.Get(context).SaveChanges();
    }
}

image

 

Now my idea is to define the Repository argument as optional and to deduce it from the parent RepositoryScope using the Properties, as I did with DisposeActivity:

public abstract class RepositoryActivity : NativeActivity
{
    public InArgument<IRepository> Repository { get; set; }
 
    protected virtual IRepository GetRepository(NativeActivityContext context)
    {
        IRepository repo = Repository.Get(context);
        if (repo == null)
            repo = ((IRepository)context.Properties.Find("Repository"));
        return repo;
    }
}
 
public class AddEntity : RepositoryActivity
{
    [RequiredArgument]
    public InArgument<object> Entity { get; set; }
 
    protected override void Execute(NativeActivityContext context)
    {
        GetRepository(context).Add(Entity.Get(context));
    }
}
 
public class SaveChanges : RepositoryActivity
{
    protected override void Execute(NativeActivityContext context)
    {
        GetRepository(context).SaveChanges();
    }
}
 
internal class DisposeActivity : RepositoryActivity
{
    protected override void Execute(NativeActivityContext context)
    {
        GetRepository(context).Dispose();
    }
}

 

Now there is another issue with it: what happens if a RepositoryScope encapsulates another RepositoryScope? I will have an issue with my property “Repository”.

So we need to improve our code. The idea here is to use a Stack<IRepository>.

In my code, I don’t like the way to use the property “Repository”. Even if I use a constant instead of using the string “Repository”, it is not good enough in my opinion, and moreover it’s a shame to need a cast. In my opinion, we should use only typed instructions for this.

So I will define a new class:

public class RepositoryContext
{
    private Stack<IRepository> _repositories;
    public Stack<IRepository> Repositories
    {
        get { return _repositories ?? (_repositories = new Stack<IRepository>()); }
    }
 
    public static RepositoryContext GetContext(NativeActivityContext context)
    {
        return (RepositoryContext)context.Properties.FirstOrDefault(kv => kv.Key == typeof(RepositoryContext).Name).Value;
    }
 
    public static RepositoryContext CreateContext(NativeActivityContext context)
    {
        RepositoryContext c = new RepositoryContext();
        context.Properties.Add(typeof(RepositoryContext).Name, c);
        return c;
    }
 
    public static RepositoryContext GetOrCreateContext(NativeActivityContext context)
    {
        return GetContext(context) ?? CreateContext(context);
    }
}

 

Now on my RespositoryScope Execute method, I will initialize it like this:

RepositoryContext.CreateContext(context).Repositories.Push(repo);

instead of

context.Properties.Add("Repository", repo);

Then I will use it like this on my RepositoryActivity:

repo = RepositoryContext.GetContext(context).Repositories.Peek();

instead of

repo = ((IRepository)context.Properties.Find("Repository"));

And I will finally pop the stack with the Dispose:

internal class DisposeActivity : RepositoryActivity
{
    protected override void Execute(NativeActivityContext context)
    {
        GetRepository(context).Dispose();
        RepositoryContext.GetContext(context).Repositories.Pop();
    }
}

 

Now, I really like it. I find this way to use properties very good and I could copy it in another case. So my idea is to factorize it so far as we can.

So I will finally use a base class:

public class WFPropertiesContext<T> where T : WFPropertiesContext<T>, new()
{
    public static T GetContext(NativeActivityContext context)
    {
        return (T)context.Properties.FirstOrDefault(kv => kv.Key == typeof(T).Name).Value;
    }
 
    public static T CreateContext(NativeActivityContext context)
    {
        T c = new T();
        c.Initialize(context);
        context.Properties.Add(typeof(T).Name, c);
        return c;
    }
 
    public static T GetOrCreateContext(NativeActivityContext context)
    {
        return GetContext(context) ?? CreateContext(context);
    }
 
    protected virtual void Initialize(NativeActivityContext context)
    {
    }
}
 
public class RepositoryContext : WFPropertiesContext<RepositoryContext>
{
    private Stack<IRepository> _repositories;
    public Stack<IRepository> Repositories
    {
        get { return _repositories ?? (_repositories = new Stack<IRepository>()); }
    }
}
Posted by Matthieu MEZIL | with no comments
Filed under: ,

EF and recursion

Through this post, I will show you a concrete case of a bad EF usage I recently saw.

My model is the following:

image_thumb3

We want to get a tree with a constraint: if the node type is "G" and the node doesn’t have any child, it should not be present on the tree.

How to do this?

This is the code I saw:

public static List<Node> GetTree()
{
using (var context = new TreeEntities())
{
List<Node> nodes = context.Nodes.Where(n => n.Parent == null).ToList();
foreach (var n in nodes)
LoadChildren(context, n);
foreach (var n in nodes.Where(n => n.Type == "G" && n.Children.Count == 0).ToList())
nodes.Remove(n);
return nodes;
} } public static void LoadChildren(TreeEntities context, Node parentNode) {
List<Node> nodes = context.Nodes.Where(n => n.ParentId == parentNode.Id).ToList();
foreach (var n in nodes)
LoadChildren(context, n);
foreach (var n in nodes.Where(n => n.Type == "G" && n.Children.Count == 0).ToList())
context.Nodes.Detach(n); }

I did a test with the following entities set:

using (var context = new TreeEntities())
{
for (int i0 = 0; i0 < 10; i0++)
{
Node n0 = new Node { Name = i0.ToString(), Type = i0 == 9 ? null : "G" };
context.Nodes.AddObject(n0);
if (i0 > 7)
break;
for (int i1 = 0; i1 < 10; i1++)
{
Node n1 = new Node { Name = n0.Name + i1.ToString(), Type = i1 == 9 ? null : "G", Parent = n0 };
context.Nodes.AddObject(n1);
if (i1 > 7)
break;
for (int i2 = 0; i2 < 10; i2++)
{
Node n2 = new Node { Name = n1.Name + i2.ToString(), Type = i2 == 9 ? null : "G", Parent = n1 };
context.Nodes.AddObject(n2);
if (i2 > 7)
break;
for (int i3 = 0; i3 < 10; i3++)
{
Node n3 = new Node { Name = n2.Name + i3.ToString(), Type = i3 == 9 ? null : "G", Parent = n2 };
context.Nodes.AddObject(n3);
if (i3 > 7)
break;
for (int i4 = 0; i4 < 10; i4++)
context.Nodes.AddObject(new Node { Name = n3.Name + i4.ToString(), Parent = n3 });
}
}
}
}
context.SaveChanges(); }

With this first version, this code runs on 5 minutes and 41 seconds and generates 46 226 SQL queries!

We don’t need to be a DBA to understand that if the number of SQL queries depends on the number of datarows, the code does not support heavy load.

So, how to improve it?

One of the big advantages using EF is the fact that it makes relationships itself. It’s why this code works. Indeed, we never set nodes Parent / Children. EF did it for us.

So you could imagine a code like this:

public static List<Node> GetTree()
{
using (var context = new TreeEntities())
{


return context.Nodes.Where(n => n.Type != "G" || n.Children.Any()).AsEnumerable().Where(n => n.ParentId == null).ToList();
} }

Before continuing: note that I use ParentId != null and not Parent != null. Indeed, when we enumerate entities, parent could not be loaded yet. If it’s the case, ParentId will be different than null but Parent will be equal to null.

Now, there is a big issue with this code: it does not work as expected. If we have a node with type equals to G with a child with type equals to G too without any child, the first node should not appear in the tree because its only one child should not appear. For this case, the previous code does not work.

SQL is not well-known for recursion.

I will try two different approaches:

  • a first one where recursion will be only on the code. Note that it means loading too many entities from DB (as the first version did).
  • a second, in a future post, where recursion is managed into the DB

My first solution is the following:

public static IEnumerable<T> GetEntitiesInCache<T>(this ObjectContext context, EntityState entityState = EntityState.Unchanged | EntityState.Modified | EntityState.Added)
{
return context.ObjectStateManager.GetObjectStateEntries(entityState).Select(ose => ose.Entity).OfType<T>(); } public static List<Node> GetTree(TreeEntities context) {
using (var context = new TreeEntities())
{
foreach (var n in context.Nodes);

bool continueLoop;
do
{
continueLoop = false;
foreach (var n in context.GetEntitiesInCache<Node>().Where(n => n.Type == "G" && n.Children.Count == 0).ToList())
{
continueLoop = true;
context.Nodes.Detach(n);
}
} while (continueLoop);

return context.GetEntitiesInCache<Node>().Where(n => n.ParentId == null).ToList();
} }

With my entities set, the 5 minutes and 41 seconds becomes only 1 second and only one SQL query (instead of 46 226) with my version.

Note that I use a local DB. Else, difference would be bigger.

Note also this code

       foreach (var n in context.Nodes);

which allows to load every nodes in the context before detaching “bad” nodes.

Posted by Matthieu MEZIL | with no comments

L2E: SQL impact

As I wrote yesterday,

“Behind the EF magic, there is the SQL reality. I’m sticking to my guns, if you don’t know SQL, you can’t write best Entity Framework queries.

I find a good sample with grouping functions.”

Yesterday, I showed you how to have a Count and a Max on only one LINQ query.

Now imagine that I want to get the orders count and the max orders date per customer.

How to do it?

If you are sure that all customers have some orders or if you want to exclude customers without any order, the best way is probably to write this:

from o in context.Orders
group o by o.Customer into g
select new { Customer = g.Key, OrdersCount = g.Count(), LastOrderDate = g.Max(o => o.OrderDate) };

In this case, the SQL is the very similar than the L2E query:

SELECT

[GroupBy1].[K1] AS [CustomerId],

[GroupBy1].[K2] AS [LastName],

[GroupBy1].[K3] AS [FirstName],

[GroupBy1].[K4] AS [BirthDay],

[GroupBy1].[K5] AS [AddressLine],

[GroupBy1].[K6] AS [City],

[GroupBy1].[K7] AS [PostalCode],

[GroupBy1].[K8] AS [Region],

[GroupBy1].[K9] AS [Country],

[GroupBy1].[A1] AS [C1],

[GroupBy1].[A2] AS [C2]

FROM ( SELECT

      [Extent2].[CustomerId] AS [K1],

      [Extent2].[LastName] AS [K2],

      [Extent2].[FirstName] AS [K3],

      [Extent2].[BirthDay] AS [K4],

      [Extent2].[AddressLine] AS [K5],

      [Extent2].[City] AS [K6],

      [Extent2].[PostalCode] AS [K7],

      [Extent2].[Region] AS [K8],

      [Extent2].[Country] AS [K9],

      COUNT(1) AS [A1],

      MAX([Extent1].[OrderDate]) AS [A2]

      FROM  [dbo].[Orders] AS [Extent1]

      INNER JOIN [dbo].[Customers] AS [Extent2] ON [Extent1].[CustomerId] = [Extent2].[CustomerId]

      GROUP BY [Extent2].[CustomerId], [Extent2].[LastName], [Extent2].[FirstName], [Extent2].[BirthDay], [Extent2].[AddressLine], [Extent2].[City], [Extent2].[PostalCode], [Extent2].[Region], [Extent2].[Country]

)  AS [GroupBy1]

 

With a basic execution plan:

image

But, if you can have some customers without any orders and you want it on the result, it is more difficult.

A basic idea would be to get previous query union customers without any orders:

(from o in context.Orders  group o by o.Customer intoselect new { Customer = g.Key, OrdersCount = g.Count(), LastOrderDate = (DateTime?)g.Max(o => o.OrderDate) }).Union(
from c in context.Customers
where !c.Orders.Any()
select new { Customer = c, OrdersCount = 0, LastOrderDate = (DateTime?)null });

In this case, the SQL query is not good:

SELECT

[Distinct1].[C1] AS [C1],

[Distinct1].[C2] AS [C2],

[Distinct1].[C3] AS [C3],

[Distinct1].[C4] AS [C4],

[Distinct1].[C5] AS [C5],

[Distinct1].[C6] AS [C6],

[Distinct1].[C7] AS [C7],

[Distinct1].[C8] AS [C8],

[Distinct1].[C9] AS [C9],