I've got a small MVC webapplication running using Entity Framework 6. When starting the application by browwsing to the homepage (eg. www.mywebsite.dev) on my dev. machine the applicationpool get's started and the page get loaded as expected.
Despithe the fact that the homepage is pretty ligthweight and only get's a few things from the database (2 menu's, 2 paragraphs with text, and a collection with 3-4 objects) the application pool is already > 200 MB (!) after just loading the homepage once..
Using this and this article i've managed to figure out how to profile the manage memory, and I also removed a few static properties blocking the disposal of the context.
The DbContext has lazy loading disabled,
public class MyContext: DbContext
{
private readonly Dictionary<Type, EntitySetBase> _mappingCache = new Dictionary<Type, EntitySetBase>();
#region dbset properties
//Membership sets
public IDbSet<UserProfile> UserProfiles { get; set; }
public IDbSet<Project> Project { get; set; }
public IDbSet<Portfolio> Portfolio { get; set; }
public IDbSet<Menu> Menu { get; set; }
public IDbSet<MenuItem> MenuItem { get; set; }
public IDbSet<Page> Page { get; set; }
public IDbSet<Component> Component { get; set; }
public IDbSet<ComponentType> ComponentType { get; set; }
public IDbSet<BlogCategory> BlogCategory { get; set; }
public IDbSet<Blog> Blog { get; set; }
public IDbSet<Caroussel> Carousel { get; set; }
public IDbSet<CarouselItem> CarouselItem { get; set; }
public IDbSet<Redirect> Redirect { get; set; }
public IDbSet<TextBlock> TextBlock { get; set; }
public IDbSet<Image> Image { get; set; }
public IDbSet<ImageContent> ImageContent { get; set; }
#endregion
/// <summary>
/// The constructor, we provide the connectionstring to be used to it's base class.
/// </summary>
public MyContext() : base("name=MyConnectionstring")
{
//Disable lazy loading by default!
Configuration.LazyLoadingEnabled = false;
Database.SetInitializer<BorloContext>(null);
}
//SOME OTHER CODE
}
I still see a lot of objects in memory from which I expect they're related to entity framework's lazy loading.
I've setup the website with a few layers;
Controller - The usual stuff
Service - Trought a using statement used in the controllers. The services are disposable and contain a UnitOfWork. The UnitOfWork is initialized in the Constructor of the service and disposed when the service itsself get's disposed.
UnitOfWOrk - The UnitOfWork class contains a readonly private variable containing the context, together with a set of properties instantiating a Generic Repository of type T. Again, the UnitOfWork is disposable and it disposes the context when the Dispose method is called.
The Generic Repository matches an interface, takes the DbContext trought it's constructor and offers a basic set of methods trough an interface.
Below an example of how this is used.
PartialController
public class PartialController : BaseController
{
//private readonly IGenericService<Menu> _menuService;
//private readonly UnitOfWork _unitOfWork = new UnitOfWork();
//private readonly MenuService _menuService;
public PartialController()
{
//_menuService = new GenericService<Menu>();
//_menuService = new MenuService();
}
/// <summary>
/// Renders the mainmenu based on the correct systemname.
/// </summary>
[ChildActionOnly]
public ActionResult MainMenu()
{
var viewModel = new MenuModel { MenuItems = new List<MenuItem>() };
try
{
Menu menu;
using (var service = ServiceFactory.GetMenuService())
{
menu= service.GetBySystemName("MainMenu");
}
//Get the menuItems collection from somewhere
if (menu.MenuItems != null && menu.MenuItems.Any())
{
viewModel.MenuItems = menu.MenuItems.ToList();
return View(viewModel);
}
}
catch (Exception exception)
{
//TODO: Make nice function of this and decide throwing or logging.
if (exception.GetType().IsAssignableFrom(typeof(KeyNotFoundException)))
{
throw;
}
else
{
//TODO: Exception handling and logging
//TODO: If exception then redirect to 500-error page.
}
}
return View(viewModel);
}
}
ServiceFactory
public class ServiceFactory
{
public static IService<Menu> GetMenuService()
{
return new MenuService();
}
}
MenuService
public class MenuService : BaseService, IService<Menu>
{
private readonly UnitOfWork _unitOfWork;
private bool _disposed;
public MenuService()
{
if (_unitOfWork == null)
{
_unitOfWork = new UnitOfWork();
}
}
/// <summary>
/// Retrieves the menu by the provided systemname.
/// </summary>
/// <param name="systemName">The systemname of the menu.</param>
/// <returns>The menu if found. Otherwise null</returns>
public Menu GetBySystemName(string systemName)
{
var menu = new Menu();
if (String.IsNullOrWhiteSpace(systemName)) throw new ArgumentNullException("systemName","Parameter is required.");
if (Cache.HasItem(systemName))
{
menu = Cache.GetItem(systemName) as Menu;
}
else
{
var retrievedMenu = _unitOfWork.MenuRepository.GetSingle(m => m.SystemName.Equals(systemName), "MenuItems,MenuItems.Page");
if (retrievedMenu == null) return menu;
try
{
var exp = GenericRepository<CORE.Entities.MenuItem>.IsPublished();
var menuItems = (exp != null) ?
retrievedMenu.MenuItems.AsQueryable().Where(exp).Select(MenuTranslator.Translate).OrderBy(mi => mi.SortOrder).ToList() :
retrievedMenu.MenuItems.Select(MenuTranslator.Translate).OrderBy(mi => mi.SortOrder).ToList();
menu.MenuItems = menuItems;
}
catch (Exception)
{
//TODO: Logging
}
Cache.AddItem(systemName, menu, CachePriority.Default, CacheDuration.Short);
}
return menu;
}
public IEnumerable<Menu> Get()
{
throw new NotImplementedException();
}
~MenuService()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_unitOfWork.Dispose();
}
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
GenericRepository
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class, IEntityObject
{
///
/// The database context used.
///
internal MyContext Context;
/// <summary>
/// The loaded set of entities.
/// </summary>
internal DbSet<TEntity> DbSet;
/// <summary>
/// The constructor taking the databasecontext.
/// </summary>
/// <param name="context">The databasecontext to use.</param>
public GenericRepository(MyContext context)
{
//Apply the context
Context = context;
//Set the entity type for the current dbset.
DbSet = context.Set<TEntity>();
}
public IQueryable<TEntity> AsQueryable(bool publishedItemsOnly = true)
{
if (!publishedItemsOnly) return DbSet;
try
{
return DbSet.Where(IsPublished());
}
catch (Exception)
{
//TODO: Logging
}
return DbSet;
}
/// <summary>
/// Gets a list of items matching the specified filter, order by and included properties.
/// </summary>
/// <param name="filter">The filter to apply.</param>
/// <param name="includeProperties">The properties to include to apply eager loading.</param>
/// <param name="publishedItemsOnly">True if only publish and active items should be included, otherwise false.</param>
/// <returns>A collection of entities matching the condition.</returns>
public virtual IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter, string includeProperties, bool publishedItemsOnly)
{
var query = AsQueryable(publishedItemsOnly);
if (filter != null)
{
query = query.Where(filter);
}
if (String.IsNullOrWhiteSpace(includeProperties))
return query;
//Include all properties to the dbset to enable eager loading.
query = includeProperties.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (current, includeProperty) => current.Include(includeProperty));
return query;
}
}
To make a very long story short. What in my code / situation could possibly cause the problem that when loading just the homepage a amazing 200 MB or more is used? One strange thing i've noticed is that just before the page is loaded the memory jumps from 111 mb to 232 MB in the example below;
EDIT
Result from trace with dotMemory
EDIT 2
Below the results after i loaded the homepage. The homepage is now empty and in the global asax a single call to one service is made. I've kept the page open for a while and then refreshed, resulting in all the peaks.
Below a more detailed result, with apperently a lot of strings taking a lot of memory..?
EDIT 3
Different view from dotMemory
So, image is much more clearer now.
dotMemory displays, that your app takes only 9Mb of memory, we can see this on the Snapshot view. This is also confirmed by Memory Traffic view. ~73Mb was allocated from the beginning of profiling and ~65Mb was already collected to the Snapshot #1 point.
What about total memory usage displayed on the real time data chart, sorry I did not realized earlier then the most of your app memory usage is generation 0 heap. (And also I missed that your app uses only ~8Mb on the snapshot tile on this screen).
Gen 0 heap size displays the maximum bytes that can be allocated in
generation 0; it does not indicate the current number of bytes
allocated in generation 0.
http://msdn.microsoft.com/en-us/library/x2tyfybc(v=vs.110).aspx
Gen 0 heap size looks abnormally big for my taste, but it is an internal details of .net garbage collector, and it has a right to do that.
I have ventured to suggest that your app is running on the computer with big amount of RAM and/or with big CPU cache. But it can be also special aspects of ASP server implementation.
Conclusion - there is no problem with your app memory usage :) At least on loading just the homepage.
P.S. I would recommend to watch dotMemory video tutorials, in order to learn how to use it
Related
I have the following code:
The structure of my project is like this:
My classes (relevant for this bug)
SizeEntity.cs
namespace DataObjects.EntityFramework
{
public class SizeEntity
{
public int Id { get; set; }
public string SizeName { get; set; }
}
}
Size.cs (on business objects class library)
namespace BusinessObjects
{
// Product business object
// ** Enterprise Design Pattern: Domain Model, Identity Field, Foreign key mapping
public class Size : BusinessObject
{
// ** Enterprise Design Pattern: Identity field pattern
public Size()
{
// establish business rules
AddRule(new ValidateRequired("SizeName"));
AddRule(new ValidateLength("SizeName", 1, 3));
}
public int Id { get; set; }
public string SizeName { get; set; }
}
}
SizeDao.cs
public class SizeDao : ISizeDao
{
/// <summary>
/// Constructor to initialize AutoMapper
/// </summary>
static SizeDao()
{
Mapper.Initialize(cfg => cfg.CreateMap<SizeEntity, Size>());
Mapper.Initialize(cfg => cfg.CreateMap<List<SizeEntity>, List<Size>>());
}
/// <summary>
/// Inserts size into database
/// </summary>
/// <param name="size"></param>
public void InsertSize(Size size)
{
using (var context = new ExamContext())
{
var entity = Mapper.Map<Size, SizeEntity>(size);
context.Sizes.Add(entity);
context.SaveChanges();
// update business object with new id
size.Id = entity.Id;
}
}
/// <summary>
/// Gets all size from database
/// </summary>
/// <returns>Returns a list of Sizes</returns>
public List<Size> GetSizes()
{
using (var context = new ExamContext())
{
var sizes = context.Sizes.ToList();
return Mapper.Map<List<SizeEntity>, List<Size>>(sizes);
}
}
}
I am getting the mapping error on the last line of this code.
return Mapper.Map, List>(sizes);
What am I missing here?
Error message is in the title:
AutoMapperMappingException: Missing type map configuration or unsupported mapping, Mapping List of Types
Update 1:
I removed that line suggest and still get:
Missing type map configuration or unsupported mapping.
Mapping types:
SizeEntity -> Size
DataObjects.EntityFramework.SizeEntity -> BusinessObjects.Size
For first you should remove the initialization of the mapping where you specify the List of objects, you just need to define the mapping with the base objects. That initialization would lead to an empty result when you call Map method.
static SizeDao()
{
Mapper.Initialize(cfg => cfg.CreateMap<SizeEntity, Size>());
// This is not needed.
// Mapper.Initialize(cfg => cfg.CreateMap<List<SizeEntity>, List<Size>>());
}
Another thing, as suggested #CodeCaster, don't put your mapping definition in the static constructor, but, for example, in the entry point of the whole application and verify that those lines get called before you invoke the Map method.
Because Automapper didnt work, then I had to do the following:
/// <summary>
/// Gets all size from database
/// </summary>
/// <returns>Returns a list of Sizes</returns>
public List<Size> GetSizes()
{
using (var context = new ExamContext())
{
var sizes = context.SizeEntities.ToList();
//Convert SizeEntities list to Size (Business Objects) list
var targetList = sizes
.Select(x => new Size() { Id = x.Id, SizeName = x.SizeName})
.ToList();
return targetList;
//return Mapper.Map<List<SizeEntity>, List<Size>>(sizes);
}
}
I have several classes: User.cs, Permission.cs, and so on... all of them are children of BaseCore.cs where all the main logic is.
Here is my dbContext class (simplified):
public class MyContext : DbContext {
public MyContext() : base("AppEntityDB") {
System.Data.Entity.Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
}
public virtual DbSet<User> Users { get; set; }
public virtual DbSet<Permissions> Permissions { get; set; }
}
Now I am creating baseListForm.cs that will be parent of all ListForms (windows form aplication)
I want baseListForm to have all basic functions like SaveData(); EditData() and LoadData();
Here is my BaseListForm class (simplified):
public partial class BaseListForm : Form {
private BaseCore _classType;
public virtual void LoadList() {
//I want here to load the collection of Mycontext() depending
// on what which class it calls. eg. if User.cs calls then i want
// to call DbSet<User> Users collection (accessible - _classType.Users)
var LoadCurrentClass = ClassType.
}
}
So I want to somehow select the corresponding collection from MyContext() for this.GetType(); class.
If you mean to say that 1 form has access to 1 dataset only then you can use generics.
You can add more functionalities other than my example. Normally you would create a repository or a unit of work class for CRUD operations on the database but this would restrict your form to access 1 DbSet. I hope you get the idea.
Form Base
public abstract class FormBase<T> : Form
where T : BaseCore
{
private ApplicationDbContext _db = new ApplicationDbContext();
/// <summary>
/// Accessor for the DbSet
/// </summary>
protected DbSet<T> DbSet
{
get { return _db.Set<T>(); }
}
/// <summary>
/// Inform the DbContext of the changes made on an entity
/// </summary>
/// <param name="entity"></param>
protected void UpdateEntry(T entity)
{
_db.Entry(entity).State = EntityState.Modified;
}
/// <summary>
/// Save changes on the DbContext
/// </summary>
protected void SaveData()
{
_db.SaveChanges();
}
}
User Form
public partial class frmUser : FormBase<User>
{
public frmUser()
{
InitializeComponent();
User user = this.DbSet.FirstOrDefault(); // Get first user record
user.Name = "New Name"; // Set new name value
this.UpdateEntry(user); // Inform DbContext that user has changed
this.SaveData(); // Save the changes made to the DbContext
}
}
I'm trying to get an auto decompile configuration for DelegateDecompiler to work, as shown here: http://daveaglick.com/posts/computed-properties-and-entity-framework
But it doesn't work :(
Not sure what I'm doing wrong.
Here's the class that has a computed value.
public class Person
{
public int Id { get; set; }
public string First { get; set; }
public string Last { get; set; }
[NotMapped]
[Computed]
public string Full { get { return First + " " + Last; } }
}
This is the configuration.
public class DelegateDecompilerConfiguration : DefaultConfiguration
{
public override bool ShouldDecompile(MemberInfo memberInfo)
{
// Automatically decompile all NotMapped members
return base.ShouldDecompile(memberInfo) || memberInfo.GetCustomAttributes(typeof(NotMappedAttribute), true).Length > 0;
}
}
I also tried removing the [NotMapped] and then changed typeof(NotMappedAttribute) to typeof(ComputedAttribute) in the above configuration.
Then I register it like so
DelegateDecompiler.Configuration.Configure(new DelegateDecompilerConfiguration());
In Startup.cs. I also tried putting it directly into my action.
public ActionResult Test()
{
DelegateDecompiler.Configuration.Configure(new DelegateDecompilerConfiguration());
var ctx = new ApplicationDbContext();
var result = ctx.People.Where(x => x.Full.Contains("foo bar")).ToList();
return View();
}
Neither work :(
If I put .Decompile() on the query then it works as expected. So the DelegateDecompiler is working, but not the configuration.
As you discovered in your GitHub issue, you always have to call .Decompile() within your LINQ query. The extra configuration just eliminates the need to decorate all of your computed properties with the Computed attribute, instead relying on the Entity Framework NotMapped attribute.
As Dave says it requires Decompile()
So you don't have to remember to call it you can create a mapper extension that wraps what you are doing.
public static class MapperExtensions
{
/// <summary>
/// Creates a list from automapper projection. Also wraps delegate decompiler to supprt Computed Domain properties
/// </summary>
/// <typeparam name="TDestination"></typeparam>
/// <param name="projectionExpression"></param>
/// <returns></returns>
public static List<TDestination>
ToList<TDestination>(this IProjectionExpression projectionExpression)
{
return projectionExpression.To<TDestination>().Decompile().ToList();
}
}
I'm currently having an issue with explicit loading in Entity Framework.
I've disabled Proxy Creation and Lazy Loading in my datacontext.
public DataContext()
: base("")
{
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
}
This has been done because I need to be able to serialize my entity framework objects, which I cannot do when using virtual properties because this creates a virtual property.
I would like to retrieve the entity 'ContentType':
[Table("T_CONTENT_TYPES")]
public class ContentType : Entity
{
#region Properties
/// <summary>
/// Gets or sets the name of the content type.
/// </summary>
[Required]
[Column(Order = 1)]
public string Name { get; set; }
/// <summary>
/// Gets or sets the <see cref="Folder"/> in which this item belongs.
/// If this field is empty, it means that this item is stored in no particular folder, which means it's stored in the root.
/// </summary>
[Column(Order = 100)]
public Folder Folder { get; set; }
#endregion
}
This is done by using this code:
var contentType = UnitOfWork.ContentTypeRepository.GetById(Id);
As you see, my entity has a reference to a Folder. Since LazyLoading & Proxy Creation is disabled, I use the following code to retrieve the folder:
((DataContext) UnitOfWork.DataContext).Entry(contentType).Reference(x => x.Folder).Load();
My complete method is then:
public ContentType GetById(int Id)
{
var contentType = UnitOfWork.ContentTypeRepository.GetById(Id);
((DataContext) UnitOfWork.DataContext).Entry(contentType).Reference(x => x.Folder).Load();
return contentType;
}
The GetById method looks like the following:
public TEntity GetById(int id)
{
if (GetEntityById != null)
{ GetEntityById(this, new EventArgs()); }
var returnData = dbSet.FirstOrDefault(x => x.Id == id);
if (GetEntityByIdFound != null)
{ GetEntityByIdFound(this, new EventArgs()); }
return returnData;
}
However, the 'Folder' property is still null.
Anyone has an idea why this is not working?
If somebody knows a good working alternative for serializing lazy loaded entities, I'm prepared to integrate that one.
I am building a Windows 8 app and I’m running into trouble with async calls. I am going to try to provide as many details as possible because I think there I 2 outcomes to this :
I am doing stuff completely wrong when it comes to the async calls
Or I am doing it wrong but also, it might me the wrong architecture that puts me up with this problem which shouldn’t be there in the first place.
I am a newby to Windows Azure and to MVVM but here’s the situation…
The app is now built for Windows 8 but I also want to be able to use other platforms, so what I have done first is creating a WebAPI project which is published to a Windows Azure Web Site. That way, I can use JSON to transfer data and the WebAPI controllers connect to a Repository that is handling data requests to and from Window Azure Table Storage. The second part is a MVVM Light Windows 8 app that requests data from the Azure Web Site.
So let’s take a more detailed look at the WebAPI project. Here I have a category model to start with.
public class Category : TableServiceEntity
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
public string Parent { get; set; }
}
The category model simply contains a Name and Description (The id is the RowKey of the
TableServiceEntity). Also a string reference is added to the parent category in case the categories are nested. First question arises : Should the Parent be of the type Category instead of string and should the Category model on the backend side have a collection of child categories??
Then I have my IRepository interface to define repositories. (Work in progress ;-)) It is also using the Specification pattern to pass query ranges. This is all working as you can test using your browser and browse to : http://homebudgettracker.azurewebsites.net/api/categories
public interface IRepository<T> where T : TableServiceEntity
{
void Add(T item);
void Delete(T item);
void Update(T item);
IEnumerable<T> Find(params Specification<T>[] specifications);
IEnumerable<T> RetrieveAll();
void SaveChanges();
}
Now that the repositories are clear, let’s take a look at the controller. I have a CategoriesController which is just an ApiController containing an IRepository repository. (Injected with Ninject but irrelevant here)
public class CategoriesController : ApiController
{
static IRepository<Category> _repository;
public CategoriesController(IRepository<Category> repository)
{
if (repository == null)
{
throw new ArgumentNullException("repository");
}
_repository = repository;
}
The controller contains a few methods like for example:
public Category GetCategoryById(string id)
{
IEnumerable<Category> categoryResults =_repository.Find(new ByRowKeySpecification(id));
if(categoryResults == null)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
if (categoryResults.First<Category>() == null)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
return categoryResults.First<Category>();
}
By now, we have seen the backend and let’s move on to the actual problem here: The MvvmLight client and the async http request to the WebAPI controller.
On the client side project, I also have a category model.
public class Category
{
[JsonProperty("PartitionKey")]
public string PartitionKey { get; set; }
[JsonProperty("RowKey")]
public string RowKey { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
[JsonProperty("Description")]
public string Description { get; set; }
[JsonProperty("Timestamp")]
public string Timestamp { get; set; }
[JsonProperty("Parent")]
public string ParentRowKey { get; set; }
public ObservableCollection<Category> Children { get; set; }
}
Don’t mind the PartitionKey and RowKey properties, the partition key should be left out because it does not concern the application what Azure table service entitiy properties exist. The RowKey could actually be renamed to Id. But not actually relevant here.
The main view’s ViewModel looks like this:
public class MainViewModel : CategoryBasedViewModel
{
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(IBudgetTrackerDataService budgetTrackerDataService)
: base(budgetTrackerDataService)
{
PageTitle = "Home budget tracker";
}
}
It extends from a ViewModel that I created to share the logic for pages that contain a Category Observable collection. The important stuff in this ViewModel:
A IBudgetTrackerDataService that is injected into the ViewModel which is a high level data service
An ObservableCollection containing a collection of category-
ViewModel-wrapped categories
Some properties for binding (fe: IsLoadingCategories to handle ProgressRing on the view)
A getCategoriesCompleted callback method called by the IBudgetTrackerDataService after
the async call will be completed
So the code is as follows:
public abstract class CategoryBasedViewModel : TitledPageViewModel
{
private IBudgetTrackerDataService _dataService;
private ObservableCollection<CategoryViewModel> _categoryCollection;
private Boolean isLoadingCategories;
public const string CategoryCollectionPropertyName = "CategoryCollection";
public const string IsLoadingCategoriesPropertyName = "IsLoadingCategories";
public Boolean IsLoadingCategories
{
get
{
return isLoadingCategories;
}
set
{
if (isLoadingCategories != value)
{
isLoadingCategories = value;
RaisePropertyChanged(IsLoadingCategoriesPropertyName);
}
}
}
public ObservableCollection<CategoryViewModel> CategoryCollection
{
get
{
return _categoryCollection;
}
set
{
_categoryCollection = value;
RaisePropertyChanged(CategoryCollectionPropertyName);
}
}
public CategoryBasedViewModel(IBudgetTrackerDataService budgetTrackerDataService)
{
wireDataService(budgetTrackerDataService);
}
public CategoryBasedViewModel(IBudgetTrackerDataService budgetTrackerDataService, string pageTitle)
{
PageTitle = pageTitle;
wireDataService(budgetTrackerDataService);
}
private void wireDataService(IBudgetTrackerDataService budgetTrackerDataService)
{
_dataService = budgetTrackerDataService;
CategoryCollection = new ObservableCollection<CategoryViewModel>();
IsLoadingCategories = true;
_dataService.GetCategoriesAsync(GetCategoriesCompleted);
}
private void GetCategoriesCompleted(IList<Category> result, Exception error)
{
if (error != null)
{
throw new Exception(error.Message, error);
}
if (result == null)
{
throw new Exception("No categories found");
}
IsLoadingCategories = false;
CategoryCollection.Clear();
foreach (Category category in result)
{
CategoryCollection.Add(new CategoryViewModel(category, _dataService));
// Added the dataService as a parameter because the CategoryViewModel will handle the search for Parent Category and Children catagories
}
}
}
This is all working but now I want the Parent/Children relation to work on categories. For this, I have
added logic to the CategoryViewModel so that it is fetching child categories when constructed…
public CategoryViewModel(Category categoryModel, IBudgetTrackerDataService
budgetTrackerDataService)
{
_category = categoryModel;
_dataService = budgetTrackerDataService;
// Retrieve all the child categories for this category
_dataService.GetCategoriesByParentAsync(_category.RowKey,
GetCategoriesByParentCompleted);
}
So the construction of a CategoryBasedViewModel is fetching categories and invokes the callback method GetCategoriesCompleted:
_dataService.GetCategoriesAsync(GetCategoriesCompleted);
That callback method is also calling the constructor of the CategoryViewModel. There, another async method is used to fetch the children of a category.
public CategoryViewModel(Category categoryModel, IBudgetTrackerDataService
budgetTrackerDataService)
{
_category = categoryModel;
_dataService = budgetTrackerDataService;
// Retrieve all the child categories for this category
_dataService.GetCategoriesByParentAsync(_category.RowKey,
GetCategoriesByParentCompleted);
}
And there is my problem! The GetCategoriesByParentAsync is an async call happening inside the other async call and the code just breaks out of the call and does nothing.
The data service implements the interface :
public interface IBudgetTrackerDataService
{
void GetCategoriesAsync(Action<IList<Category>, Exception> callback);
void GetCategoriesByParentAsync(string parent, Action<IList<Category>,
Exception> callback);
}
The async methods contain the following code:
public async void GetCategoriesAsync(Action<IList<Category>, Exception> callback)
{
// Let the HTTP client request the data
IEnumerable<Category> categoryEnumerable = await _client.GetAllCategories();
// Invoke the callback function passed to this operation
callback(categoryEnumerable.ToList<Category>(), null);
}
public async void GetCategoriesByParentAsync(string parent, Action<IList<Category>,
Exception> callback)
{
// Let the HTTP client request the data
IEnumerable<Category> categoryEnumerable = await
_client.GetCategoriesWithParent(parent);
// Invoke the callback function passed to this operation
callback(categoryEnumerable.ToList<Category>(), null);
}
Long story short:
Why do these calls fail when nesting the calls?
Secondly, Am I being stupid and should I handle the parent / children
relation of the cagegories differently?
I'm going to sidestep the parent/child relation question for now and just address the async issues.
First, there are a couple general guidelines for async code that I explain in detail in my async/await intro blog post:
Avoid async void (return Task or Task<T> instead).
Use ConfigureAwait(false) when applicable.
I've seen the callback delegate approach taken by others but I'm not sure where it's coming from. It doesn't work well with async and only serves to complicate the code, IMO. The Task<T> type was designed to represent a result value coupled with an Exception and it works seamlessly with await.
So first, your data service:
public interface IBudgetTrackerDataService
{
Task<IList<Category>> GetCategoriesAsync();
Task<IList<Category>> GetCategoriesByParentAsync(string parent);
}
public async Task<IList<Category>> GetCategoriesAsync()
{
// Let the HTTP client request the data
IEnumerable<Category> categoryEnumerable = await _client.GetAllCategories().ConfigureAwait(false);
return categoryEnumerable.ToList();
}
public async Task<IList<Category>> GetCategoriesByParentAsync(string parent)
{
// Let the HTTP client request the data
IEnumerable<Category> categoryEnumerable = await _client.GetCategoriesWithParent(parent).ConfigureAwait(false);
return categoryEnumerable.ToList();
}
or better yet, if you don't actually need IList<T>:
public interface IBudgetTrackerDataService
{
Task<IEnumerable<Category>> GetCategoriesAsync();
Task<IEnumerable<Category>> GetCategoriesByParentAsync(string parent);
}
public Task<IEnumerable<Category>> GetCategoriesAsync()
{
// Let the HTTP client request the data
return _client.GetAllCategories();
}
public Task<IEnumerable<Category>> GetCategoriesByParentAsync(string parent)
{
// Let the HTTP client request the data
return _client.GetCategoriesWithParent(parent);
}
(and at that point, you may question what purpose your data service is serving).
Moving on to the MVVM async issues: async doesn't play especially well with constructors. I've got a blog post coming out in a few weeks that will address this in detail, but here's the gist:
My personal preference is to use asynchronous factory methods (e.g., public static async Task<MyType> CreateAsync()) but this isn't always possible especially if you're using DI/IoC for your VMs.
In this case, I like to expose a property on my types that require asynchronous initialization (actually, I use an IAsyncInitialization interface, but for your code a convention will work just as well): public Task Initialized { get; }.
This property is set only once in the constructor, like this:
public CategoryViewModel(Category categoryModel, IBudgetTrackerDataService budgetTrackerDataService)
{
_category = categoryModel;
_dataService = budgetTrackerDataService;
// Retrieve all the child categories for this category
Initialized = InitializeAsync();
}
private async Task InitializeAsync()
{
var categories = await _dataService.GetCategoriesByParentAsync(_category.RowKey);
...
}
You then have the option of having your "parent" VM wait for its "child" VMs to initialize. It's not clear that this is what you want, but I'll assume that you want IsLoadingCategories to be true until all the child VMs have loaded:
public CategoryBasedViewModel(IBudgetTrackerDataService budgetTrackerDataService)
{
_dataService = budgetTrackerDataService;
CategoryCollection = new ObservableCollection<CategoryViewModel>();
IsLoadingCategories = true;
Initialized = InitializeAsync();
NotifyOnInitializationErrorAsync();
}
private async Task InitializeAsync()
{
var categories = await _dataService.GetCategoriesAsync();
CategoryCollection.Clear();
foreach (var category in categories)
{
CategoryCollection.Add(new CategoryViewModel(category, _dataService));
}
// Wait until all CategoryViewModels have completed initializing.
await Task.WhenAll(CategoryCollection.Select(category => category.Initialized));
IsLoadingCategories = false;
}
private async Task NotifyOnInitializationErrorAsync()
{
try
{
await Initialized;
}
catch
{
NotifyPropertyChanged("InitializationError");
throw;
}
}
public string InitializationError { get { return Initialized.Exception.InnerException.Message; } }
I added the InitializationError and NotifyOnInitializationErrorAsync to demonstrate one way to surface any errors that may happen during initialization. Since Task doesn't implement INotifyPropertyChanged, there's no automatic notification if/when the initialization fails, so you have to surface it explicitly.