EF Core - why Explicit Loading is terribly slow? - domain-driven-design

I am working on microservices in .Net Core with the domain driven design. The infrastructure layer has EF Core DbContext to access the database and in my repositories, I have async methods to retrieve data.
Because Include/ThenInclude does not support filtering (at least not up to Ef Core 2.1), I have tried all the possible approaches I found when googling on how to replace Include. I watched the Pluralsight videos about Ef Core too and when I saw the Explicit Loading option I was really happy due to its ability to filter related objects, but when I rewrote one of the methods into Explicit version, the query that ran for a couple of milliseconds went up to a couple of minutes!
In my entity configurations I set up all the navigations and foreign keys, but I am not sure whether explicit loading requires any additional setup or not? Before recommending to use global filters, please note that the Where clause is usually longer, so the below example is just a shortened version of the actual filtering!
This is how my methods look like (TransferService serves as the aggregate, TransferServiceDetail and any other classes are just entities within the TransferService domain):
public async Task<IEnumerable<TransferService>> GetAllAsync(
TransferServiceFilter transferServiceFilter)
{
int? pageIndex = null;
int? itemsPerPage = null;
IEnumerable<TransferService> filteredList = DBContext.TransferServices.Where(
ts => !ts.IsDeleted); //This one itself is quick.
//This is just our filtering, it does not affect performance.
if (transferServiceFilter != null)
{
pageIndex = transferServiceFilter.PageIndex;
itemsPerPage = transferServiceFilter.ItemsPerPage;
filteredList = filteredList.Where(f =>
(transferServiceFilter.TransferSupplierId == null ||
f.TransferSupplierId == transferServiceFilter.TransferSupplierId) &&
(transferServiceFilter.TransferDestinationId == null ||
f.TransferDestinationId == transferServiceFilter.TransferDestinationId) &&
(transferServiceFilter.TransferSupplierId == null ||
f.TransferSupplierId == transferServiceFilter.TransferSupplierId) &&
(string.IsNullOrEmpty(transferServiceFilter.TransportHubRef) ||
f.NormalizeReference(f.TransportHubRef) ==
f.NormalizeReference(transferServiceFilter.TransportHubRef)));
}
//This is just for paging and again, this is quick.
return await FilterList(filteredList.AsQueryable(), pageIndex, itemsPerPage);
}
public async Task<IEnumerable<TransferService>> GetAllWithServiceDetailsAsync(
TransferServiceFilter transferServiceFilter)
{
IEnumerable<TransferService> returnList = await GetAllAsync(
transferServiceFilter);
//This might be the problem as I need to iterate through my TransferServices
//to be able to load all TransferServiceDetails that belong to each individual
//Service.
foreach (TransferService service in returnList)
{
await DBContext.Entry<TransferService>(service)
.Collection(ts => ts.TransferServiceDetails.Where(
tsd => !tsd.IsDeleted)).LoadAsync();
}
return returnList;
}
In my repository I have other methods as well, similarly referring to a previous GetAllXY... method (TransferServiceDetails have Rates, Rates have Periods, etc...).
My idea was to simply call GetAllAsync when I only need TransferService data (and alone this method is lightning quick), or call GetAllWithServiceDetailsAsync when I also need the Details of the selected Services, etc, but the lower I go in this parent-child hierarchy, the slower the execution becomes and I am talking about minutes, not just a couple of extra milliseconds, or in worst case seconds.
So my question again: is there any additional setting that I might have missed from the entity configurations that explicit loading requires, or simply my queries are incorrect? Or maybe explicit loading is only good when there is only one TransferService as a parent instead of a list of TransferServices (50-100 in my case) and also there are only just a few children related entities (in my case I usually have 5-10 Details, each Detail has 2-3 Rates, each Rate has exactly 1 Period, etc...)?

I guess your filtering can't be converted to SQL Where and all filtering happens client-side (EF loads ALL TransferServices entities into memory, filters in-memory and drops mismatched).
I may check this by enabling detailed (debug) logging - EF will dump SQLs into log.
After you confirm, your should make improvements:
First, put ifs out of Where. Instead of:
filteredList = filteredList.Where(f => transferServiceFilter.TransferSupplierId == null ||
f.TransferSupplierId == transferServiceFilter.TransferSupplierId)
use
if (transferServiceFilter.TransferSupplierId != null)
{
filteredList = filteredList.Where(f => f.TransferSupplierId == transferServiceFilter.TransferSupplierId)
}
Second, you should re-think NormalizeReference. This can't be executed server-side, because SQL server doesn't know about this implementation. You should pre-normalize TransportHubRef, save it in DB (say, NormalizedTransportHubRef) and use Where with simple equality.
(Also, don't forget about indexes).

Related

Application Insights - Tracking user and session across schemas

Following https://learn.microsoft.com/en-us/azure/application-insights/app-insights-usage-send-user-context, I thought it would be easy to get cross-schema tracking of a user. However, I'm finding the absolute opposite.
I created the telemetry initializer (which the document has bugs in it hardcore):
public void Initialize(ITelemetry telemetry)
{
if (HttpContext.Current?.Session == null)
return;
if (HttpContext.Current.Session["UserId"] == null)
{
HttpContext.Current.Session["UserId"] = Guid.NewGuid().ToString();
}
telemetry.Context.User.Id = (string)HttpContext.Current.Session["UserId"];
telemetry.Context.Session.Id = HttpContext.Current.Session.SessionID;
var authUser = _sessionManager.GetAuthenticatedUser<UserDetails>();
if (authUser != null)
{
telemetry.Context.User.AuthenticatedUserId = authUser.UserId;
}
}
Then I went and added it to App Insights
TelemetryConfiguration.Active.TelemetryInitializers.Add(new UserTrackingTelemetryInitializer());
I then played with my site, expecting this stuff to start showing up. It did not. I continued to get random strings for user_Id and session_Id (things like NVhLF and what not). So, I thought, okay, maybe it's logging before I update those values? I went and inserted my initializer first:
TelemetryConfiguration.Active.TelemetryInitializers.Insert(0, new UserTrackingTelemetryInitializer());
Same thing. So I started to look at schemas I don't usually look at. Nothing. So I pulled up traces and I found it. Finally, there is where my data is going. But the other schemas don't have the updated values, so what use is this? While traces is showing the expected values for user_Id and session_Id, the others continue to show garbage. Am I doing something wrong?
The document you followed does not work indeed, a feedback has been submitted here.
Just for your reference, the way I can find to set these values is that use such TrackEvent() / TrackRequest() or other Trackxxx() methods after implemented your own telemetry initializer

Orchard background task not persisting PartRecords to the database

I'm trying to use a background task to gather Likes/Comments from the Facebook Graph APi and use that to drive our blog's trending.
Here the trendingModels have already been populated and are being used to fill in the TrendingParts.GraphId and TrendingParts.TrendingValue.
I'm not getting any exceptions and the properties on TrendingPart point to the fields in the TrendingPartRecord.
Yet nothing persists to the database, any ideas why?
_orchardsServices is IOrchardServices
var articleParts = _orchardService.ContentManager.GetMany<TrendingPart>(
trendingModels.Select(r => r.OrchardId).ToList(),
VersionOptions.Published,
QueryHints.Empty);
// Cycle through the records and update them from the matching model
foreach (var articlePart in articleParts)
{
ArticleTrendingModel trendingModel = trendingModels.Where(r => r.OrchardId == articlePart.Id).FirstOrDefault();
if(trendingModel != null)
{
// Not persisting to the database, WHY?
// What's missing?
// If I'm understanding things properly nHibernate should push this to the db autoMagically.
articlePart.GraphId = trendingModel.GraphId;
articlePart.TrendingValue = trendingModel.TrendingValue;
}
}
Edit:
It's probably worth noting that I can update and publish the fields on the TrendingPart in the admin panel but the saved changes don't appear in the MyModule_TrendingPartRecord table.
The solution was to change my Service to a transient dependency using ITransientDependency.
The service was holding a reference to the PartRecords array and because it was treated as a Singleton it never disposed and the push to the database was never made.

Defalut XmlSiteMapProvider implementation cannot use SiteMap.FindSiteMapNode?

I just upgrade MvcSiteMapProvider from v3 to v4.6.3.
I see the upgrade note indicate:
In general, any reference to System.Web.SiteMap.Provider will need to be updated to MvcSiteMapProvider.SiteMaps.Current
I am trying to get the sitemap node by using:
SiteMaps.Current.FindSiteMapNode(rawUrl)
But it always return null
I looked into the code. In the sitemap it's actually calling the function:
protected virtual ISiteMapNode FindSiteMapNodeFromUrlMatch(IUrlKey urlToMatch)
{
if (this.urlTable.ContainsKey(urlToMatch))
{
return this.urlTable[urlToMatch];
}
return null;
}
It's trying to find a match in the urlTable.
I am using Default implementation of XmlSiteMapProvider .
It define var url = node.GetAttributeValue("url");
siteMapNode.Url = url;
siteMapNode.UrlResolver = node.GetAttributeValue("urlResolver");
So if I did not define url or urlResolver attribute in the .sitemap file. These variables a set to empty string, when generate the node.
And when this nodes are passed to AddNode function in SiteMap.
When adding the node
bool isMvcUrl = string.IsNullOrEmpty(node.UnresolvedUrl) && this.UsesDefaultUrlResolver(node);
this code will check if there is url or urlResolver
// Only store URLs if they are clickable and are configured using the Url
// property or provided by a custom URL resolver.
if (!isMvcUrl && node.Clickable)
{
url = this.siteMapChildStateFactory.CreateUrlKey(node);
// Check for duplicates (including matching or empty host names).
if (this.urlTable
.Where(k => string.Equals(k.Key.RootRelativeUrl, url.RootRelativeUrl, StringComparison.OrdinalIgnoreCase))
.Where(k => string.IsNullOrEmpty(k.Key.HostName) || string.IsNullOrEmpty(url.HostName) || string.Equals(k.Key.HostName, url.HostName, StringComparison.OrdinalIgnoreCase))
.Count() > 0)
{
var absoluteUrl = this.urlPath.ResolveUrl(node.UnresolvedUrl, string.IsNullOrEmpty(node.Protocol) ? Uri.UriSchemeHttp : node.Protocol, node.HostName);
throw new InvalidOperationException(string.Format(Resources.Messages.MultipleNodesWithIdenticalUrl, absoluteUrl));
}
}
// Add the URL
if (url != null)
{
this.urlTable[url] = node;
}
Finally no url is add to the urlTable, which result in FindSiteMapNode cannot find anything.
I am not sure if there needs to be specific configuration. Or should I implement custom XmlSiteMapProvider just add the url.
ISiteMapNodeProvider instances cannot use the FindSiteMapNode function for 2 reasons. The first you have already discovered is that finding by URL can only be done if you set the url attribute explicitly in the node configuration. The second reason is that the SiteMapBuilder doesn't add any of the nodes to the SiteMap until all of the ISiteMapNodeProvider instances have completed running, so it would be moot to add the URL to the URL table anyway.
It might help if you explain what you are trying to accomplish.
The ISiteMapNodeProvider classes have complete control over the data that is added to the SiteMapNode instances and they also have access to their parent SiteMapNode instance. This is generally all that is needed in order to populate the data. Looking up another SiteMapNode from the SiteMap object while populating the data is not supported. But as long as the node you are interested in is populated in the same ISiteMapNodeProvider instance, you can just get a reference to it later by storing it in a variable.
Update
Okay, I reread your question and your comment and it now just seems like you are looking in the wrong place. MvcSiteMapProvider v4 is no longer based on Microsoft's SiteMap provider model, so using XmlSiteMapProvider doesn't make sense, as it would sidestep the entire implementation. The only case where this might make sense is if you have a hybrid ASP.NET and ASP.NET MVC application that you want to keep a consitant menu structure between. See Upgrading from v3 to v4.
There are 2 stages to working with the data. The first stage (the ISiteMapBuilder and ISiteMapNodeProvider) loads the data from various sources (XML, .NET attributes, DynamicNodeProviders, and custom implementations of ISiteMapNodeProvider) and adds it to an object graph that starts at the SiteMap object. Much like Microsoft's model, this data is stored in a shared cache and only loaded when the cache expires. This is the stage you have been focusing on and it definitely doesn't make sense to lookup nodes here.
The second stage is when an individual request is made to access the data. This is where looking up data based on a URL might make sense, but there is already a built-in CurrentNode property that finds the node matching the current URL (or more likely the current route since we are dealing with MVC) which in most cases is the best approach to finding a node. Each node has a ParentNode and ChildNodes properties that can be used to walk up or down the tree from there.
In this second stage, you can access the SiteMap data at any point after the Application_Start event such as within a controller action, in one of the built in HTML helpers, an HTML helper template in the /Views/Shared/DisplayTemplates/ directory, or a custom HTML helper. This is the point in the application life cycle which you might call the lines SiteMaps.Current.FindSiteMapNode(rawUrl) or (more likely) SiteMaps.Current.CurrentNode to get an instance of the node so you can inspect its Attributes property (the custom attributes).
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
var currentNode = MvcSiteMapProvider.SiteMaps.Current.CurrentNode;
string permission = currentNode.Attributes.ContainsKey("permission") ? currentNode.Attributes["permission"].ToString() : string.Empty;
string programs = currentNode.Attributes.ContainsKey("programs") ? currentNode.Attributes["programs"].ToString() : string.Empty;
string agencies = currentNode.Attributes.ContainsKey("agencies") ? currentNode.Attributes["agencies"].ToString() : string.Empty;
// Do something with the custom attributes of the About page here
return View();
}
The most common usage of custom attributes is to use them from within a custom HTML helper template. Here is a custom version of the /Views/Shared/DisplayTemplates/SiteMapNodeModel.cshtml template that displays the custom attributes. Note that this template is called recursively by the Menu, SiteMapPath, and SiteMap HTML helpers. Have a look at this answer for more help if HTML helper customization is what you intend to do.
#model MvcSiteMapProvider.Web.Html.Models.SiteMapNodeModel
#using System.Web.Mvc.Html
#using MvcSiteMapProvider.Web.Html.Models
#if (Model.IsCurrentNode && Model.SourceMetadata["HtmlHelper"].ToString() != "MvcSiteMapProvider.Web.Html.MenuHelper") {
<text>#Model.Title</text>
} else if (Model.IsClickable) {
if (string.IsNullOrEmpty(Model.Description))
{
#Model.Title
}
else
{
#Model.Title
}
} else {
<text>#Model.Title</text>
}
#string permission = Model.Attributes.ContainsKey("permission") ? Model.Attributes["permission"].ToString() : string.Empty
#string programs = Model.Attributes.ContainsKey("programs") ? Model.Attributes["programs"].ToString() : string.Empty
#string agencies = Model.Attributes.ContainsKey("agencies") ? Model.Attributes["agencies"].ToString() : string.Empty
<div>#permission</div>
<div>#programs</div>
<div>#agencies</div>

Processing an emaillist async in MVC4

I'm trying to make my MVC4-website check to see if people should be alerted with an email because they haven't done something.
I'm having a hard time figuring out how to approach this. I checked if the shared hosting platform would allow me to activate some sort of cronjob, but this is not available.
So now my idea is to perform this check on each page-request, which already seems suboptimal (because of the overhead). But I thought that with using an async it would not be in the way of people just visiting the site.
I first tried to do this in the Application_BeginRequest method in Global.asax, but then it gets called multiple times per page-request, so that didn't work.
Next I found that I can make a Global Filter which executes on OnResultExecuted, which would seemed promising, but still it's no go.
The problem I get there is that I'm using MVCMailer to send the mails, and when I execute it I get the error: {"Value cannot be null.\r\nParameter name: httpContext"}
This probably means that mailer needs the context.
The code I now have in my global filter is the following:
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
HandleEmptyProfileAlerts();
}
private void HandleEmptyProfileAlerts()
{
new Thread(() =>
{
bool active = false;
new UserMailer().AlertFirst("bla#bla.com").Send();
DB db = new DB();
DateTime CutoffDate = DateTime.Now.AddDays(-5);
var ProfilesToAlert = db.UserProfiles.Where(x => x.CreatedOn < CutoffDate && !x.ProfileActive && x.AlertsSent.Where(y => y.AlertType == "First").Count() == 0).ToList();
foreach (UserProfile up in ProfilesToAlert)
{
if (active)
{
new UserMailer().AlertFirst(up.UserName).Send();
up.AlertsSent.Add(new UserAlert { AlertType = "First", DateSent = DateTime.Now, UserProfileID = up.UserId });
}
else
System.Diagnostics.Debug.WriteLine(up.UserName);
}
db.SaveChanges();
}).Start();
}
So my question is, am I going about this the right way, and if so, how can I make sure that MVCMailer gets the right context?
The usual way to do this kind of thing is to have a single background thread that periodically does the checks you're interested in.
You would start the thread from Application_Start(). It's common to use a database to queue and store work items, although it can also be done in memory if it's better for your app.

Is it necessary to cache the data for a lazy loaded property with Subsonic 3 simple repository?

I have added a lazyloaded property called Orders on my Customer class. Do you think it's wise to cache the data in a private field?
private IList<Order> _orders;
[SubSonicIgnore]
public IList<Order> Orders
{
get
{
if (_orders == null)
{
var repository = new SimpleRepository("MyConnectionString", SimpleRepositoryOptions.None);
_orders = repository.Find<Order>(x => x.CustomerId == this.CustomerId);
}
return _orders;
}
}
Or is it better to not cache it like so:
[SubSonicIgnore]
public IList<Order> Orders
{
get
{
var repository = new SimpleRepository("MyConnectionString", SimpleRepositoryOptions.None);
return repository.Find<Order>(x => x.CustomerId == this.CustomerId);
}
}
The reason I'm asking is because I think it's a good idea to cache the data for performance sake, but at the same time I'm affraid that caching the data can cause it to become out-of-sync of some other process inserts/deletes records from database.
In your case, your cached Orders will exist for the lifetime of your Customers object. If you needed to clear the cached orders, you could simply requery for your Customer.
If I were you, I'd add an additional property whose name specifies that there is caching, add a custom cacheScope object (like transactionScope, the cache only exists as long as the scope exists), or specify in the documentation which properties will perform caching of child objects and for how long.
I would not remove caching. I'd leave it in there as an additional property. You'll have it if you need it.
Thanks for showing your caching logic. Here's mine. In my case, the life expectancy of my parent object is short, I don't expect >100 records of total parent/child data, and I do expect that all the child data will be used. If my data changes, then I'll need to readdress the caching logic I use in this particular instance:
private static List<HostHeader> _cachedHostHeaders;
public List<HostHeader> CachedHostHeaders
{
get
{
if (_cachedHostHeaders == null)
_cachedHostHeaders = this.HostHeaders.ToList();
return _cachedHostHeaders.Where(i => i.SiteID == this.ID).ToList();
}
}

Resources