Does EF Core Have a Query Que? - entity-framework

I am new to EF Core and am trying to figure out why I keep having to increase my SQl timeout setting to make a specific query work. In the process I developed a theory. What if EF has a que of pending requests and when there are too many it just stops working instead of removing older requests from the que. That might explain why increasing timeout time is the only way to keep my browser from showing "err connection closed" when I load pages using a specific query.
I tried posting the query on the Microsoft Q and A website with my problem a couple days ago (https://learn.microsoft.com/en-us/answers/questions/601858/aspnet-core-app-only-works-for-few-minutes-after-r.html), but as usual Microsoft has not responded with an explanation that specifically explains why their product does not work and how to fix it. The best responses I've gotten are vague suggestions that don't really explain what to do.
For instance, one guy suggest not using ToArray() but doesn't say how the same functionality can be obtained without it. Another guy suggests researching connection resiliency but doesn't say how doing that might solve my problem.
So far I have managed to keep the app running longer without this problem occurring by using LazyCache to cache results of the problem query, but after a couple days the app is having the same problem again. Why is it that Microsoft is quick to tell people not to do something, but never tells people what to do instead?
UPDATE: Since Microsoft requires people to sign into their account to access the above link and nobody here want to sign in here is part of what is posted there. It is an error message from running my app locally
An unhandled exception occurred while processing the request.
Win32Exception: The wait operation timed out.
Unknown location
SqlException: Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
Microsoft.Data.SqlClient.SqlCommand+<>c.<ExecuteDbDataReaderAsync>b__169_0(Task<SqlDataReader> result)
Stack Query Cookies Headers Routing
Win32Exception: The wait operation timed out.
Show raw exception details
SqlException: Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
Microsoft.Data.SqlClient.SqlCommand+<>c.<ExecuteDbDataReaderAsync>b_169_0(Task<SqlDataReader> result)
System.Threading.Tasks.ContinuationResultTaskFromResultTask<TAntecedentResult, TResult>.InnerInvoke()
System.Threading.Tasks.Task+<>c.<.cctor>b277_0(object obj)
System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, object state)
System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref Task currentTaskSlot, Thread threadPoolThread)
Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable<T>+AsyncEnumerator.InitializeReaderAsync(DbContext , bool result, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync<TState, TResult>(Func<DbContext, TState, CancellationToken, Task<TResult>> operation, Func<DbContext, TState, CancellationToken, Task<ExecutionResult<TResult>>> verifySucceeded, TState state, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync<TState, TResult>(Func<DbContext, TState, CancellationToken, Task<TResult>> operation, Func<DbContext, TState, CancellationToken, Task<ExecutionResult<TResult>>> verifySucceeded, TState state, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable<T>+AsyncEnumerator.MoveNextAsync()
Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync<TSource>(IQueryable<TSource> source, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync<TSource>(IQueryable<TSource> source, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToArrayAsync<TSource>(IQueryable<TSource> source, CancellationToken cancellationToken)
PostAlmostAnything.SiteServices.PostsService.GetPosts() in PostsService.cs
}
public async Task<Posts[]> GetPosts()
{
IAppCache cache = new CachingService(CachingService.DefaultCacheProvider) { DefaultCachePolicy = new CacheDefaults { DefaultCacheDurationSeconds = GlobalStatic.SITEPOSTSCACHEDURATION() } };
string cacheKey = $"GetPosts";
var cacheobject = await (from post in _context.Posts
where post.Active == (bool?)true && post.Adminban == (bool?)false && post.Site == (int?)GlobalStatic.SITENUMBER()
select post into pts
select new Posts
{
Postid = pts.Postid,
Title = pts.Title,
PostAlmostAnything.SiteServices.PostsService.GetPaginatedResult(int currentPage, int pageSize) in PostsService.cs
Sc2url = linkgenerator.subcategory2link($"{pts.CategoryNavigation.Categoryurl}", $"{pts.SubcategoryNavigation.Subcategoryurl}", $"{pts.Subcategory2Navigation.Subcategory2url}")
}
}).ToArrayAsync();
}
public async Task<List<Posts>> GetPaginatedResult(int currentPage, int pageSize)
{
return (await GetPosts()).OrderByDescending((Posts d) => d.Postid).Skip((currentPage - 1) * pageSize).Take(pageSize)
.ToList();
}
public async Task<List<Posts>> GetPaginatedResultByCategoryCity(string catslug, string waslug, string regionslug, string cityslug, int currentPage, int pageSize)
{
return (from d in await GetPosts()
PostAlmostAnything.Pages.IndexModel.OnGetAsync() in Index.cshtml.cs
{
PostService = postService;
}
public async Task<PageResult> OnGetAsync()
{
Posts = await PostService.GetPaginatedResult(CurrentPage, PageSize);
return Page();
}
}
}
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory+GenericTaskHandlerMethod.Convert<T>(object taskAsObject)
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory+GenericTaskHandlerMethod.Execute(object receiver, object[] arguments)
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync()
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g_Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>gAwaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>gLogged|17_1(ResourceInvoker invoker)
Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g_AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
UPDATE: Here is where I am now as far as trying to paginate the query. I cannot find a way to use an IQueryable and access it with an async task without getting errors for lacking GetAwaiter
public IQueryable<Posts> GetPosts(bool active)
{
var query = _context.Posts.Where(post => post.Active == active && post.Adminban == (bool?)false && post.Site == GlobalStatic.SITENUMBER());
return query;
}
public IQueryable<Posts> GetPaginatedResult(bool active, int currentPage, int pageSize)
{
//IAppCache cache = new CachingService(CachingService.DefaultCacheProvider) { DefaultCachePolicy = new CacheDefaults { DefaultCacheDurationSeconds = GlobalStatic.SITEPOSTSCACHEDURATION() } };
//string cacheKey = $"GetPosts";
var cacheobject = GetPosts(active)
.Select(pts => new Posts
{
Postid = pts.Postid,
Title = pts.Title,
Description = pts.Description,
Dateposted = pts.Dateposted,
Datemodified = pts.Datemodified,
Video = pts.Video,
Videostream = pts.Videostream,
Location = pts.Location,
Tags = pts.Tags,
Cap = pts.Cap,
Titletag = pts.Titletag,
Metatag = pts.Metatag,
Link = pts.Link,
Linkurl = pts.Linkurl,
Category = pts.Category,
Subcategory = pts.Subcategory,
Subcategory2 = pts.Subcategory2,
Worldarea = pts.Worldarea,
Region = pts.Region,
City = pts.City,
Sendemail = pts.Sendemail,
Userid = pts.Userid,
Active = pts.Active,
Adminban = pts.Adminban,
Posturl = linkgenerator.postlink($"{pts.Postid}", $"{pts.Title}"),
Comments = (from cm in pts.Comments
where cm.Active == (bool?)true && cm.Adminblock == (bool?)false
select cm into cmts
select new Comments
{
Commentid = cmts.Commentid,
Comment = cmts.Comment,
Date = cmts.Date,
Active = cmts.Active,
Adminblock = cmts.Adminblock,
Userid = cmts.Userid,
Post = cmts.Post,
Ratings = cmts.Ratings.Select((Ratings rts) => new Ratings
{
Commentnumber = rts.Commentnumber,
Rating = rts.Rating
}).ToArray(),
Images = cmts.Images.Select((Images imgs) => new Images
{
Image = imgs.Image,
Imagename = imgs.Imagename
}).ToArray(),
Likes = cmts.Likes.Select((Likes lks) => new Likes
{
Likeid = lks.Likeid,
Commentid = lks.Commentid
}).ToArray()
}).ToArray(),
Commentcount = pts.Comments.Where((Comments cm) => cm.Active == (bool?)true && cm.Adminblock == (bool?)false).Count(),
Ratings = (from r in pts.Ratings
where r.CommentnumberNavigation.Active == (bool?)true && r.CommentnumberNavigation.Adminblock == (bool?)false
select r into rtgs
select new Ratings
{
Ratingid = rtgs.Ratingid,
Rating = rtgs.Rating,
Daterated = rtgs.Daterated,
CommentnumberNavigation = new Comments
{
Commentid = rtgs.CommentnumberNavigation.Commentid
}
}).ToArray(),
Ratingcount = pts.Ratings.Where((Ratings rc) => rc.CommentnumberNavigation.Active == (bool?)true && rc.CommentnumberNavigation.Adminblock == (bool?)false).Count(),
Ratingavg = ratingavg((from rac in pts.Ratings
where rac.CommentnumberNavigation.Active == (bool?)true && rac.CommentnumberNavigation.Adminblock == (bool?)false
select rac into ra
select ra.Rating).Average()),
Images = pts.Images.Select((Images imgs) => new Images
{
Imageid = imgs.Imageid,
Imagename = imgs.Imagename,
Image = imgs.Image,
Imagetype = imgs.Imagetype,
Postid = imgs.Postid,
Comment = imgs.Comment,
Userid = imgs.Userid
}).ToArray(),
Primaryimage = computations.primaryimage((from pic in pts.Images
where pic.Imagetype == (int?)1
select pic into pi
select new Images
{
Imageid = pi.Imageid,
Image = pi.Image,
Imagename = pi.Imagename
}).DefaultIfEmpty().First()),
Likes = pts.Likes.Select((Likes lks) => new Likes
{
Likeid = lks.Likeid,
Post = lks.Post,
Commentid = lks.Commentid
}).ToArray(),
Likecount = pts.Likes.Where((Likes lc) => lc.Commentid == null).Count(),
WorldareaNavigation = new Worldarea
{
Worldarea1 = pts.WorldareaNavigation.Worldarea1,
Worldareaacronym = pts.WorldareaNavigation.Worldareaacronym,
Wurl = linkgenerator.worldarealink($"{pts.WorldareaNavigation.Worldareaacronym}")
},
RegionNavigation = new Regions
{
Regionname = pts.RegionNavigation.Regionname,
Regionacronym = pts.RegionNavigation.Regionacronym,
Rurl = linkgenerator.regionlink($"{pts.WorldareaNavigation.Worldareaacronym}", $"{pts.RegionNavigation.Regionacronym}")
},
CityNavigation = new Cities
{
City = pts.CityNavigation.City,
Cityacronym = pts.CityNavigation.Cityacronym,
Cityurl = linkgenerator.citylink($"{pts.WorldareaNavigation.Worldareaacronym}", $"{pts.RegionNavigation.Regionacronym}", $"{pts.CityNavigation.Cityacronym}")
},
CategoryNavigation = new Categories
{
Categoryname = pts.CategoryNavigation.Categoryname,
Categorylongname = pts.CategoryNavigation.Categorylongname,
Categorytitle = pts.CategoryNavigation.Categorytitle,
Categorydescription = pts.CategoryNavigation.Categorydescription,
Categorykeywords = pts.CategoryNavigation.Categorykeywords,
Categorymeta = pts.CategoryNavigation.Categorymeta,
Categoryurl = pts.CategoryNavigation.Categoryurl,
Curl = linkgenerator.categorylink($"{pts.CategoryNavigation.Categoryurl}")
},
SubcategoryNavigation = new Subcategories
{
Subcategoryname = pts.SubcategoryNavigation.Subcategoryname,
Subcategorylongname = pts.SubcategoryNavigation.Subcategorylongname,
Subcategorytitle = pts.SubcategoryNavigation.Subcategorytitle,
Subcategorydescription = pts.SubcategoryNavigation.Subcategorydescription,
Subcategorymeta = pts.SubcategoryNavigation.Subcategorymeta,
Subcategorykeywords = pts.SubcategoryNavigation.Subcategorykeywords,
Subcategoryurl = pts.SubcategoryNavigation.Subcategoryurl,
Scurl = linkgenerator.subcategorylink($"{pts.CategoryNavigation.Categoryurl}", $"{pts.SubcategoryNavigation.Subcategoryurl}")
},
Subcategory2Navigation = new Subcategory2
{
Subcategory2name = pts.Subcategory2Navigation.Subcategory2name,
Subcategory2longname = pts.Subcategory2Navigation.Subcategory2longname,
Subcategory2title = pts.Subcategory2Navigation.Subcategory2title,
Subcategory2description = pts.Subcategory2Navigation.Subcategory2description,
Subcategory2meta = pts.Subcategory2Navigation.Subcategory2meta,
Subcategory2keywords = pts.Subcategory2Navigation.Subcategory2keywords,
Subcategory2url = pts.Subcategory2Navigation.Subcategory2url,
Sc2url = linkgenerator.subcategory2link($"{pts.CategoryNavigation.Categoryurl}", $"{pts.SubcategoryNavigation.Subcategoryurl}", $"{pts.Subcategory2Navigation.Subcategory2url}")
}
}).OrderByDescending((Posts d) => d.Postid).Skip((currentPage - 1) * pageSize).Take(pageSize);
//Func<List<Posts>> actionThatWeWantToCache = () => cacheobject;
//var CachedAdapter = cache.GetOrAdd(cacheKey, actionThatWeWantToCache);
return cacheobject;
}```

Ok, here's your problem with code like this:
public async Task<Posts[]> GetPosts()
{
IAppCache cache = new CachingService(CachingService.DefaultCacheProvider) { DefaultCachePolicy = new CacheDefaults { DefaultCacheDurationSeconds = GlobalStatic.SITEPOSTSCACHEDURATION() } };
string cacheKey = $"GetPosts";
var cacheobject = await (from post in _context.Posts
where post.Active == (bool?)true && post.Adminban == (bool?)false && post.Site == (int?)GlobalStatic.SITENUMBER()
select post into pts
select new Posts
{
Postid = pts.Postid,
Title = pts.Title,
Sc2url = linkgenerator.subcategory2link($"{pts.CategoryNavigation.Categoryurl}", $"{pts.SubcategoryNavigation.Subcategoryurl}", $"{pts.Subcategory2Navigation.Subcategory2url}")
}
}).ToArrayAsync();
}
public async Task<List<Posts>> GetPaginatedResult(int currentPage, int pageSize)
{
return (await GetPosts()).OrderByDescending((Posts d) => d.Postid).Skip((currentPage - 1) * pageSize).Take(pageSize)
.ToList();
}
GetPosts is doing a ToArray which will look to materialize your ENTIRE table into entities BEFORE doing pagination.... This is also calling C# code like this linkgenerator(...) method which will trigger client-side evaluation which is a huge performance no-no since that will also materialize the related entities for all rows. You are then selecting the Post Entities into another class called Posts which is either a Post entity instance or a separate DTO/ViewModel in order to capture this link.
This is a common sin when I see developers pursuing DNRY (Do Not Repeat Yourself) to keep common logic in one place, and then employ patterns that treat everything consistently for consistency's sake.
GetPosts() is a dangerous method if it returns an array of posts as it would always return everything where the code you are calling wants to optimize for paginated results. This is very common in Generic implementations which force a one-size-fits-all on operations. Some tables will be small enough that a "GetAll" type operation is reasonable safe, however that common method against another table can halt a system for no good reason.
To satisfy DNRY, centralize the common query into a private method that can be called as needed. A public GetAll() method for Posts is probably never needed for this entity so I would remove it:
private IQueryable<Post> getPosts()
{
var query = _context.Posts
.Where(post => post.Active
&& !post.Adminban
&& post.Site == GlobalStatic.SITENUMBER());
return query;
}
This method can be called by every method that wants to query across Posts and their related details. This centralizes your common rules around active state, and tenancy. (ownership)
Now to your GetPaginatedResult method. Here you're going to want to ensure you are selecting your desired data. I would recommend declaring a ViewModel for the post data given you want to have some computed values.
[Serializable]
public class PostViewModel
{
public int PostId { get; set; }
public string Title { get; set; }
public string CategoryUrl { get; set; }
public string SubcategoryUrl { get; set; }
public string Subcategory2Url { get; set; }
public string Sc2Url
{
get { return linkgenerator.subcategory2link(CategoryUrl, SubcategoryUrl, Subcategory2url); }
}
}
Within Linq expressions we want to select all of the raw data from the entity and associated other entities. Where we want to compose/calculate values, we can expose getters in the view model that do this calculation as needed using the raw data.
And for the GetPaginatedResult method:
public async Task<List<Posts>> GetPaginatedResult(int currentPage, int pageSize)
{
var posts = await getPosts()
.Select(x => new PostViewModel
{
PostId = x.PostId,
Title = x.Title,
CategoryUrl = x.CategoryNavigation.Categoryurl,
SubcategoryUrl = x.SubcategoryNavigation.Subcategoryurl,
SubcategoryUrl = x.Subcategory2Navigation.Subcategory2url
}).Skip((currentPage -1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
Note that the Select only selects the raw values from the Post and associated entities. It doesn't attempt to calculate the full link. The view model will take care of that when the property is accessed, such as when it is referenced to compose a view or serialized. You can perform simple computed fields in the Linq expression, but only basic stuff like appending strings together with "+" and some simple functions that EF can translate. Even String.Format (including $"{value}") is off the cards inside a Linq expression fed to EF. It cannot translate that to SQL so it would fall on client-side evaluation and you want to avoid that at all costs. By default with EF Core 2, client-side evaluation was on by default, with EF Core 3.1 onward it is off by default but some teams turn it on. I highly recommend leaving it off as it's a bigger landmine than lazy loading.
That should eliminate your performance issues. This generates a compact SQL statement taking into account the desired pagination and serializes just those results into a view model to return to the view. The core business rules around ownership can be cetralized, and by exposing IQueryable it is left to the consuming code to project or further modify the results (paginate, etc.) to build an efficient query. Don't expose any method that you don't intend to get called. (Like a GetAll())
Edit: It sounds like you are trying to replace:
public async Task<Posts[]> GetPosts()
with
public async IQueryable<Post> GetPosts()
This is *not the objective. The objective is to centralize the core logic into a protected method which GetPosts as well as GetPaginatedResult can both call rather than having GetPaginatedResult calling GetPosts which is loading all data. You also need to address that you cannot call linkgenerator from within the Select expression without triggering client-side evaluation so the suggestion there is to either define view models for the results, or if you insist on returning the entity models as a view model, adding a non-mapped property for the generated URL.
public async Task<Posts[]> GetPosts()
{
return await _getPosts()
.Select(pts => new Posts
{
Postid = pts.Postid,
Title = pts.Title,
CategoryUrl = pts.CategoryNavigation.CategoryUrl,
SubcategoryUrl = pts.SubcategoryNavigation.SubcategoryUrl,
Subcategory2Url = pts.SubcategoryNavigation.Subcategory2Url
}).ToArrayAsync();
}
public async Task<List<Posts>> GetPaginatedResult(int currentPage, int pageSize)
{
return await _getPosts()
.Select(pts => new Posts
{
Postid = pts.Postid,
Title = pts.Title,
CategoryUrl = pts.CategoryNavigation.CategoryUrl,
SubcategoryUrl = pts.SubcategoryNavigation.SubcategoryUrl,
Subcategory2Url = pts.SubcategoryNavigation.Subcategory2Url
}).Skip((currentPage-1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
Where _getPosts() is a new protected IQueryable method that centralizes the core rules around your isActive and Site. I put an Underscore before the method name to differentiate it from your public GetPosts() method which is probably called in a number of different places.
You initially want to avoid changing the signature of your public methods because this would break anywhere and everywhere else that called this method, where it is better to correct this issue a bit at a time where it is most needed.
Looking at your updated example you don't want to change your public methods to IQueryable, leave those as your original return types and just ensure that these methods that call the new IQueryable method do their respective await with ToArrayAsync or ToListAsync respectively instead of calling the public GetPosts() method. The change you will still need to make is to move ALL properties where you are calling "linkgenerator" or any similar C# objects/functions into unmapped properties and instead use Select to retrieve the raw values these calls will need.

Related

storing object in cosmos db returns bad request?

I seem to be unable to store a simple object to cosmos db?
this is the database model.
public class HbModel
{
public Guid id { get; set; }
public string FormName { get; set; }
public Dictionary<string, object> Form { get; set; }
}
and this is how I store the data into the database
private static void SeedData(HbModelContext dbContext)
{
var cosmosClient = dbContext.Database.GetCosmosClient();
cosmosClient.ClientOptions.AllowBulkExecution = true;
if (dbContext.Set<HbModel>().FirstOrDefault() == null)
{
// No items could be picked hence try seeding.
var container = cosmosClient.GetContainer("hb", "hb_forms");
HbModel first = new HbModel()
{
Id = Guid.NewGuid(),//Guid.Parse(x["guid"] as string),
FormName = "asda",//x["name"] as string,
Form = new Dictionary<string, object>() //
}
string partitionKey = await GetPartitionKey(container.Database, container.Id);
var response = await container.CreateItemAsync(first, new PartitionKey(partitionKey));
}
else
{
Console.WriteLine("Already have data");
}
}
private static async Task<string> GetPartitionKey(Database database, string containerName)
{
var query = new QueryDefinition("select * from c where c.id = #id")
.WithParameter("#id", containerName);
using var iterator = database.GetContainerQueryIterator<ContainerProperties>(query);
while (iterator.HasMoreResults)
{
foreach (var container in await iterator.ReadNextAsync())
{
return container.PartitionKeyPath;
}
}
return null;
}
but when creating the item I get this error message
A host error has occurred during startup operation '3b06df1f-000c-4223-a374-ca1dc48d59d1'.
[2022-07-11T15:02:12.071Z] Microsoft.Azure.Cosmos.Client: Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: 24bac0ba-f1f7-411f-bc57-3f91110c4528; Reason: ();.
Value cannot be null. (Parameter 'provider')
no idea why it fails?
the data should not be formatted incorreclty?
It also fails in case there is data in the dictionary.
What is going wrong?
There are several things wrong with the attached code.
You are enabling Bulk but you are not following the Bulk pattern
cosmosClient.ClientOptions.AllowBulkExecution = true is being set, but you are not parallelizing work. If you are going to use Bulk, make sure you are following the documentation and creating lists of concurrent Tasks. Reference: https://learn.microsoft.com/azure/cosmos-db/sql/tutorial-sql-api-dotnet-bulk-import#step-6-populate-a-list-of-concurrent-tasks. Otherwise don't use Bulk.
You are blocking threads.
The call to container.CreateItemAsync(first, new PartitionKey("/__partitionKey")).Result; is a blocking call, this can lead you to deadlocks. When using async operations (such as CreateItemAsync) please use the async/await pattern. Reference: https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#avoid-using-taskresult-and-taskwait
The PartitionKey parameter should be the value not the definition.
On the call container.CreateItemAsync(first, new PartitionKey("/__partitionKey")) the Partition Key (second parameter) should be the value. Assuming your container has a Partition Key Definition of /__partitionKey then your documents should have a __partitionKey property and you should pass the Value in this parameter of such property in the current document. Reference: https://learn.microsoft.com/azure/cosmos-db/sql/troubleshoot-bad-request#wrong-partition-key-value
Optionally, if your documents do not contain such a value, just remove the parameter from the call:
container.CreateItemAsync(first)
Be advised though that this solution will not scale, you need to design your database with Partitioning in mind: https://learn.microsoft.com/azure/cosmos-db/partitioning-overview#choose-partitionkey
Missing id
The model has Id but Cosmos DB requires id, make sure the content of the document contains id when serialized.

EF Core System.ObjectDisposedException

I am getting this error mentioned in the title. The same code works for the first API call, but not for the 2nd call. It fails on the 2nd SaveChangesAsync() call
await dbContext.SaveChangesAsync().ConfigureAwait(false);
I am using
services.AddTransient<ProjectContext>();
in my startup.cs. I tried AddScope but it doesn't work.
Error:
System.ObjectDisposedException: 'Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'ProjectContext'.'
Code:
private readonly Action<InjectedControllerArgs, ProjectDto, NavigationElements> _postAfterSave = async (injectedControllerArgs, dto, nav) => {
var memberClient = new MemberClient { MemberId = member.MemberId,
InvitationEmail = member.Email,
ClientId = dto.ClientId,
LastModifierId = member.MemberId };
await dbContext.MemberClient!.AddAsync(memberClient).ConfigureAwait(false);
await dbContext.SaveChangesAsync().ConfigureAwait(false);
var role = await dbContext.Role.Where(r => "client-admin" == r.RoleCode)
.FirstOrDefaultAsync()
.ConfigureAwait(false);
await dbContext.MemberClientRole!.AddAsync(new MemberClientRole
{ LastModifierId = member.MemberId,
MemberClientId = memberClient.MemberClientId,
RoleId = role.RoleId })
.ConfigureAwait(false);
await dbContext.SaveChangesAsync().ConfigureAwait(false); }
public async Task<ActionResult<ProjectDto>> PostInsertAsync(int customerId, int clientId, ProjectDto dto)
{
if (dto == null) throw new ArgumentNullException(nameof(dto));
dto.CustomerId = customerId;
dto.ClientId = clientId;
return await _pmBaseController.PostAsync(new NavigationElements(customerId, clientId, null), Topic, dto,
_returnOne, _postBeforeSave, _postAfterSave).ConfigureAwait(false);
}
public async Task<IActionResult> PutAsync(NavigationElements nav,
TopicName topic,
int id,
TDto dto,
Func<InjectedControllerArgs, NavigationElements, int, Task<T>> returnOne,
Func<TDto, int, TDto>? putBeforeSave = null,
Action<InjectedControllerArgs, TDto, NavigationElements>? putAfterSave = null
) ...
Your _postAfterSave delegate is asynchronous but does not return a Task which effectively means it is async void so your _pmBaseController is unable to await it which means execution will proceed before this Action is done. That the first SaveChanges works is timing / coincidence.
Try changing the type from Action<InjectedControllerArgs, ProjectDto, NavigationElements> to Func<InjectedControllerArgs, ProjectDto, NavigationElements, Task> and make sure to await it everywhere.

ASP.NET Core Entity Framework SQL Query SELECT

I am one of the many struggling to "upgrade" from ASP.NET to ASP.NET Core.
In the ASP.NET project, I made database calls from my DAL like so:
var result = context.Database.SqlQuery<Object_VM>("EXEC [sp_Object_GetByKey] #Key",
new SqlParameter("#Key", Key))
.FirstOrDefault();
return result;
My viewmodel has additional fields that my object does not, such as aggregates of related tables. It seems unnecessary and counter intuitive to include such fields in a database / table structure. My stored procedure calculates all those things and returns the fields as should be displayed, but not stored.
I see that ASP.NET Core has removed this functionality. I am trying to continue to use stored procedures and load view models (and thus not have the entity in the database). I see options like the following, but as a result I get "2", the number of rows being returned (or another mysterious result?).
using(context)
{
string cmd = "EXEC [sp_Object_getAll]";
var result = context.Database.ExecuteSQLCommand(cmd);
}
But that won't work because context.Database.ExecuteSQLCommand is only for altering the database, not "selecting".
I've also seen the following as a solution, but the code will not compile for me, as "set" is really set<TEntity>, and there isn't a database entity for this viewmodel.
var result = context.Set().FromSql("EXEC [sp_Object_getAll]");
Any assistance much appreciated.
Solution:
(per Tseng's advice)
On the GitHub Entity Framework Issues page, there is a discussion about this problem. One user recommends creating your own class to handle this sort of requests, and another adds an additional method that makes it run smoother. I changed the methods slights to accept slightly different params.
Here is my adaptation (very little difference), for others that are also looking for a solution:
Method in DAL
public JsonResult GetObjectByID(int ID)
{
SqlParameter[] parms = new SqlParameter[] { new SqlParameter("#ID", ID) };
var result = RDFacadeExtensions.GetModelFromQuery<Object_List_VM>(context, "EXEC [sp_Object_GetList] #ID", parms);
return new JsonResult(result.ToList(), setting);
}
Additional Class
public static class RDFacadeExtensions
{
public static RelationalDataReader ExecuteSqlQuery(
this DatabaseFacade databaseFacade,
string sql,
SqlParameter[] parameters)
{
var concurrencyDetector = databaseFacade.GetService<IConcurrencyDetector>();
using (concurrencyDetector.EnterCriticalSection())
{
var rawSqlCommand = databaseFacade
.GetService<IRawSqlCommandBuilder>()
.Build(sql, parameters);
return rawSqlCommand
.RelationalCommand
.ExecuteReader(
databaseFacade.GetService<IRelationalConnection>(),
parameterValues: rawSqlCommand.ParameterValues);
}
}
public static IEnumerable<T> GetModelFromQuery<T>(
DbContext context,
string sql,
SqlParameter[] parameters)
where T : new()
{
DatabaseFacade databaseFacade = new DatabaseFacade(context);
using (DbDataReader dr = databaseFacade.ExecuteSqlQuery(sql, parameters).DbDataReader)
{
List<T> lst = new List<T>();
PropertyInfo[] props = typeof(T).GetProperties();
while (dr.Read())
{
T t = new T();
IEnumerable<string> actualNames = dr.GetColumnSchema().Select(o => o.ColumnName);
for (int i = 0; i < props.Length; ++i)
{
PropertyInfo pi = props[i];
if (!pi.CanWrite) continue;
System.ComponentModel.DataAnnotations.Schema.ColumnAttribute ca = pi.GetCustomAttribute(typeof(System.ComponentModel.DataAnnotations.Schema.ColumnAttribute)) as System.ComponentModel.DataAnnotations.Schema.ColumnAttribute;
string name = ca?.Name ?? pi.Name;
if (pi == null) continue;
if (!actualNames.Contains(name)) { continue; }
object value = dr[name];
Type pt = pi.DeclaringType;
bool nullable = pt.GetTypeInfo().IsGenericType && pt.GetGenericTypeDefinition() == typeof(Nullable<>);
if (value == DBNull.Value) { value = null; }
if (value == null && pt.GetTypeInfo().IsValueType && !nullable)
{ value = Activator.CreateInstance(pt); }
pi.SetValue(t, value);
}//for i
lst.Add(t);
}//while
return lst;
}//using dr
}

Entity Framework + ODATA: side-stepping the pagination

The project I'm working on has the Entity Framework on top of an OData layer. The Odata layer has it's server side pagination turned to a value of 75. My reading on the subject leads me to believe that this pagination value is used across the board, rather than a per table basis. The table that I'm currently looking to extract all the data from is, of course, more than 75 rows. Using the entity framework, my code is simply thus:
public IQueryable<ProductColor> GetProductColors()
{
return db.ProductColors;
}
where db is the entity context. This is returning the first 75 records. I read something where I could append a parameter inlinecount set to allpages giving me the following code:
public IQueryable<ProductColor> GetProductColors()
{
return db.ProductColors.AddQueryOption("inlinecount","allpages");
}
However, this too returns 75 rows!
Can anyone shed light on how to truly get all the records regardless of the OData server-side pagination stuff?
important: I cannot remove the pagination or turn it off! It's extremely valuable in other scenarios where performance is a concern.
Update:
Through some more searching I've found an MSDN that describes how to do this task.
I'd love to be able to turn it into a full Generic method but, this was as close as I could get to a generic without using reflection:
public IQueryable<T> TakeAll<T>(QueryOperationResponse<T> qor)
{
var collection = new List<T>();
DataServiceQueryContinuation<T> next = null;
QueryOperationResponse<T> response = qor;
do
{
if (next != null)
{
response = db.Execute<T>(next) as QueryOperationResponse<T>;
}
foreach (var elem in response)
{
collection.Add(elem);
}
} while ((next = response.GetContinuation()) != null);
return collection.AsQueryable();
}
calling it like:
public IQueryable<ProductColor> GetProductColors()
{
QueryOperationResponse<ProductColor> response = db.ProductColors.Execute() as QueryOperationResponse<ProductColor>;
var productColors = this.TakeAll<ProductColor>(response);
return productColors.AsQueryable();
}
If unable turn off paging you'll receive 75 row by call, always. You can get all rows in following ways:
Add another IQueryable<ProductColor> AllProductColors and modify
public static void InitializeService(DataServiceConfiguration config)
{
config.UseVerboseErrors = true;
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.SetEntitySetPageSize("ProductColors", 75); - Note only paged queries are present
config.SetServiceOperationAccessRule("*", ServiceOperationRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
You should call ProductColors as many as needed, for example
var cat = new NetflixCatalog(new Uri("http://odata.netflix.com/v1/Catalog/"));
var x = from t in cat.Titles
where t.ReleaseYear == 2009
select t;
var response = (QueryOperationResponse<Title>)((DataServiceQuery<Title>)x).Execute();
while (true)
{
foreach (Title title in response)
{
Console.WriteLine(title.Name);
}
var continuation = response.GetContinuation();
if (continuation == null)
{
break;
}
response = cat.Execute(continuation);
}
I use Rx with following code
public sealed class DataSequence<TEntry> : IObservable<TEntry>
{
private readonly DataServiceContext context;
private readonly Logger logger = LogManager.GetCurrentClassLogger();
private readonly IQueryable<TEntry> query;
public DataSequence(IQueryable<TEntry> query, DataServiceContext context)
{
this.query = query;
this.context = context;
}
public IDisposable Subscribe(IObserver<TEntry> observer)
{
QueryOperationResponse<TEntry> response;
try
{
response = (QueryOperationResponse<TEntry>)((DataServiceQuery<TEntry>)query).Execute();
if (response == null)
{
return Disposable.Empty;
}
}
catch (Exception ex)
{
logger.Error(ex);
return Disposable.Empty;
}
var initialState = new State
{
CanContinue = true,
Response = response
};
IObservable<TEntry> sequence = Observable.Generate(
initialState,
state => state.CanContinue,
MoveToNextState,
GetCurrentValue,
Scheduler.ThreadPool).Merge();
return new CompositeDisposable(initialState, sequence.Subscribe(observer));
}
private static IObservable<TEntry> GetCurrentValue(State state)
{
if (state.Response == null)
{
return Observable.Empty<TEntry>();
}
return state.Response.ToObservable();
}
private State MoveToNextState(State state)
{
DataServiceQueryContinuation<TEntry> continuation = state.Response.GetContinuation();
if (continuation == null)
{
state.CanContinue = false;
return state;
}
QueryOperationResponse<TEntry> response;
try
{
response = context.Execute(continuation);
}
catch (Exception)
{
state.CanContinue = false;
return state;
}
state.Response = response;
return state;
}
private sealed class State : IDisposable
{
public bool CanContinue { get; set; }
public QueryOperationResponse<TEntry> Response { get; set; }
public void Dispose()
{
CanContinue = false;
}
}
}
so for get any data thru OData, create a sequence and Rx does the rest
var sequence = new DataSequence<Product>(context.Products, context);
sequence.OnErrorResumeNext(Observable.Empty<Product>())
.ObserveOnDispatcher().SubscribeOn(Scheduler.NewThread).Subscribe(AddProduct, logger.Error);
The page size is set by the service author and can be set per entity set (but a service may choose to apply the same page size to all entity sets). There's no way to avoid it from the client (which is by design since it's a security feature).
The inlinecount option asks the server to include the total count of the results (just the number), it doesn't disable the paging.
From the client the only way to read all the data is to issue the request which will return the first page and it may contain a next link which you request to read the next page and so on until the last response doesn't have the next link.
If you're using the WCF Data Services client library it has support for continuations (the next link) and a simple sample can be found in this blog post (for example): http://blogs.msdn.com/b/phaniraj/archive/2010/04/25/server-driven-paging-with-wcf-data-services.aspx

Generating Cache Keys from IQueryable For Caching Results of EF Code First Queries

I'm trying to implement a caching scheme for my EF Repository similar to the one blogged here. As the author and commenters have reported the limitation is that the key generation method cannot produce cache keys that vary with a given query's parameters. Here is the cache key generation method:
private static string GetKey<T>(IQueryable<T> query)
{
string key = string.Concat(query.ToString(), "\n\r",
typeof(T).AssemblyQualifiedName);
return key;
}
So the following queries will yield the same cache key:
var isActive = true;
var query = context.Products
.OrderBy(one => one.ProductNumber)
.Where(one => one.IsActive == isActive).AsCacheable();
and
var isActive = false;
var query = context.Products
.OrderBy(one => one.ProductNumber)
.Where(one => one.IsActive == isActive).AsCacheable();
Notice that the only difference is that isActive = true in the first query and isActive = false in the second.
Any suggestions/insight to efficiently generating cache keys which vary by IQueryable parameters would be truly appreciated.
Kudos to Sergey Barskiy for sharing the EF CodeFirst caching scheme.
Update
I took the approach of traversing the IQueryable's expression tree myself with the goal of resolving the values of the parameters used in the query. With maxlego's suggestion, I extended the System.Linq.Expressions.ExpressionVisitor class to visit the expression nodes that we're interested in - in this case, the MemberExpression. The updated GetKey method looks something like this:
public static string GetKey<T>(IQueryable<T> query)
{
var keyBuilder = new StringBuilder(query.ToString());
var queryParamVisitor = new QueryParameterVisitor(keyBuilder);
queryParamVisitor.GetQueryParameters(query.Expression);
keyBuilder.Append("\n\r");
keyBuilder.Append(typeof (T).AssemblyQualifiedName);
return keyBuilder.ToString();
}
And the QueryParameterVisitor class, which was inspired by the answers of Bryan Watts and Marc Gravell to this question, looks like this:
/// <summary>
/// <see cref="ExpressionVisitor"/> subclass which encapsulates logic to
/// traverse an expression tree and resolve all the query parameter values
/// </summary>
internal class QueryParameterVisitor : ExpressionVisitor
{
public QueryParameterVisitor(StringBuilder sb)
{
QueryParamBuilder = sb;
Visited = new Dictionary<int, bool>();
}
protected StringBuilder QueryParamBuilder { get; set; }
protected Dictionary<int, bool> Visited { get; set; }
public StringBuilder GetQueryParameters(Expression expression)
{
Visit(expression);
return QueryParamBuilder;
}
private static object GetMemberValue(MemberExpression memberExpression, Dictionary<int, bool> visited)
{
object value;
if (!TryGetMemberValue(memberExpression, out value, visited))
{
UnaryExpression objectMember = Expression.Convert(memberExpression, typeof (object));
Expression<Func<object>> getterLambda = Expression.Lambda<Func<object>>(objectMember);
Func<object> getter = null;
try
{
getter = getterLambda.Compile();
}
catch (InvalidOperationException)
{
}
if (getter != null) value = getter();
}
return value;
}
private static bool TryGetMemberValue(Expression expression, out object value, Dictionary<int, bool> visited)
{
if (expression == null)
{
// used for static fields, etc
value = null;
return true;
}
// Mark this node as visited (processed)
int expressionHash = expression.GetHashCode();
if (!visited.ContainsKey(expressionHash))
{
visited.Add(expressionHash, true);
}
// Get Member Value, recurse if necessary
switch (expression.NodeType)
{
case ExpressionType.Constant:
value = ((ConstantExpression) expression).Value;
return true;
case ExpressionType.MemberAccess:
var me = (MemberExpression) expression;
object target;
if (TryGetMemberValue(me.Expression, out target, visited))
{
// instance target
switch (me.Member.MemberType)
{
case MemberTypes.Field:
value = ((FieldInfo) me.Member).GetValue(target);
return true;
case MemberTypes.Property:
value = ((PropertyInfo) me.Member).GetValue(target, null);
return true;
}
}
break;
}
// Could not retrieve value
value = null;
return false;
}
protected override Expression VisitMember(MemberExpression node)
{
// Only process nodes that haven't been processed before, this could happen because our traversal
// is depth-first and will "visit" the nodes in the subtree before this method (VisitMember) does
if (!Visited.ContainsKey(node.GetHashCode()))
{
object value = GetMemberValue(node, Visited);
if (value != null)
{
QueryParamBuilder.Append("\n\r");
QueryParamBuilder.Append(value.ToString());
}
}
return base.VisitMember(node);
}
}
I'm still doing some performance profiling on the cache key generation and hoping that it isn't too expensive (I'll update the question with the results once I have them). I'll leave the question open, in case anyone has suggestions on how to optimize this process or has a recommendation for a more efficient method for generating cache keys with vary with the query parameters. Although this method produces the desired output, it is by no means optimal.
i suggest to use ExpressionVisitor
http://msdn.microsoft.com/en-us/library/bb882521(v=vs.90).aspx
Just for the record, "Caching the results of LINQ queries" works well with the EF and it's able to work with parameters correctly, so it can be considered as a good second level cache implementation for EF.
While the solution of the OP works quite well, I found that the performance of the solution is a little bit poor.
The duration of the key generation varied between 300ms and 1200ms for my queries.
However, I've found another solution that has quite better performance (<10ms).
public static string ToTraceString<T>(DbQuery<T> query)
{
var internalQueryField = query.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(f => f.Name.Equals("_internalQuery")).FirstOrDefault();
var internalQuery = internalQueryField.GetValue(query);
var objectQueryField = internalQuery.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(f => f.Name.Equals("_objectQuery")).FirstOrDefault();
var objectQuery = objectQueryField.GetValue(internalQuery) as ObjectQuery<T>;
return ToTraceStringWithParameters(objectQuery);
}
private static string ToTraceStringWithParameters<T>(ObjectQuery<T> query)
{
string traceString = query.ToTraceString() + "\n";
foreach (var parameter in query.Parameters)
{
traceString += parameter.Name + " [" + parameter.ParameterType.FullName + "] = " + parameter.Value + "\n";
}
return traceString;
}