Caching in Automapper Resolver to increase performance - caching

As follows, I m using auto mapper to convert tasks
Mapper.CreateMap<Task, GanttTask>().ForMember(dst => dst.parentIdRaw, opt => opt.ResolveUsing<TaskParentIdResolver>())
so in TaskParentIdResolver,
public class TaskParentIdResolver : ValueResolver<Task, int?>
{
protected IUow Uow { get; set; }
public TaskParentIdResolver()
{
RepositoryFactories factory = new RepositoryFactories();
IRepositoryProvider provider = new RepositoryProvider(factory);
this.Uow = new Uow(provider);
}
protected override int? ResolveCore(Task source)
{
if (source.ParentId != null && source.ParentId != Guid.Empty)
{
var task = Uow.Tasks.GetById(source.ParentId.Value);
return task.Id;
}
else return null;
}
}
The problem i faced is,
var tasksList = Mapper.Map<IEnumerable<Task>, IEnumerable<GanttTask>>(Uow.Tasks.GetAll().Where(con => con.IsProjectSummary == true)).ToList();
If i have 200 task then resolver called as 200 times and each time Uow takes time to return to resolve the parentId. Is there any way to cache this task list in resolver so that only first UOw calls the task and all the other times it returns the cache list of tasks.

Don't put caching in AutoMapper, put it in whatever service that AutoMapper uses. If you're using an ORM, use the caching available in the ORM.

Related

Why does Resolver behave differently in Query vs Mutation?

I've been struggling with the issue/behaviour described below for while now and can't seem to figure out what's going on. This is all based on inherited/adapted code, so it's completely possible that I'm missing something fundamental. It's been literally years since I've asked a question on SO, so please bear that in mind if I'm not complying with the current expectations on formatting and content/detail.
I've got a number of domain classes involved in this problem, but at a high level, what I'm trying to do is to count the number of users with a specific role in relation to a group.
The behaviour that I'm seeing when I execute a GraphQL query (See below), is that the attribute in question (capacityUsed) is populated as expected, which makes me believe that the wiring is correct.
However, when I try to access the attribute within a GraphQL mutation, the underlying data to populate the value isn't retrieved, so the default value is returned, which isn't accurate or what I want.
Ultimately what I'm trying to do is to access (and make business decisions based on) the GroupRoleCapacity.CapacityUsed attribute. I'd like to sort out what's wrong with the config/setup for the mutation so that my resolver works as expected in both situations.
I'd appreciate any help or insight into what might be causing this. I've gone through the related HotCholocate and GraphL documentation a number of times and tried just about every iteration of related keywords that I can think of searching for an explanation of what's going on, and haven't been able to find a solution.
Here's how things are currently wired up (pulling out extraneous info/attributes to keep it short):
//GraphQL Entity Configuration
public class GroupRoleCapacityType : ObjectType<GroupRoleCapacity>
{
protected override void Configure(IObjectTypeDescriptor<GroupRoleCapacity> descriptor)
{
descriptor
.Field(grc => grc.Group)
.ResolveWith<Resolvers>(r => r.GetGroup(default!, default!))
.UseDbContext<AppDbContext>()
.Description("This is the Group for this GroupRoleCapacity.");
descriptor
.Field(grc => grc.Role)
.ResolveWith<Resolvers>(r => r.GetRole(default!, default!))
.UseDbContext<AppDbContext>()
.Description("This is the Role for this GroupRoleCapacity.");
descriptor
.Field(grc => grc.CapacityUsed)
.ResolveWith<Resolvers>(r => r.GetCapacityUsed(default!, default!))
.UseDbContext<AppDbContext>()
.Description("This is the number of Users with the indicated Role for this GroupRoleCapacity.");
}
protected class Resolvers
{
public Group GetGroup([Parent] GroupRoleCapacity GroupRoleCapacity, [ScopedService] AppDbContext context)
{
return context.Groups.FirstOrDefault(p => p.Id == GroupRoleCapacity.GroupId);
}
public Role GetRole([Parent] GroupRoleCapacity GroupRoleCapacity, [ScopedService] AppDbContext context)
{
return context.Roles.FirstOrDefault(p => p.Id == GroupRoleCapacity.RoleId);
}
public int GetCapacityUsed([Parent] GroupRoleCapacity GroupRoleCapacity, [ScopedService] AppDbContext context)
{
return context.Users.Count(u => u.GroupId == GroupRoleCapacity.GroupId &&
u.UserRoles.Any(ur => ur.RoleId == GroupRoleCapacity.RoleId));
}
}
}
//GraphQL Service Startup
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) { Configuration = configuration; }
public void ConfigureServices(IServiceCollection services) {
services.AddScoped(p => p.GetRequiredService<IDbContextFactory<AppDbContext>>().CreateDbContext());
services
.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.AddType<GroupType>()
.AddType<RoleType>()
.AddType<UserType>()
.AddType<UserRoleType>()
.AddType<GroupRoleCapacityType>()
}
}
Using the query below seems to work as expected and the GroupRoleCapacity.CapacityUsed attribute is populated appropriately when accessing the data via a query
{ groupRoleCapacity { groupId roleId capacityUsed }}
//GraphQL Query
public class Query
{
[UseDbContext(typeof(AppDbContext))]
[GraphQLType(typeof(NonNullType<ListType<NonNullType<GroupRoleCapacities.GroupRoleCapacityType>>>))]
public IQueryable<GroupRoleCapacity> GetGroupRoleCapacity([ScopedService] AppDbContext context)
{
return context.GroupRoleCapacities;
}
}
However, when I use a mutation similar to below, GroupRoleCapacity.CapacityUsed isn't being set correctly, which is what's making me suspect a problem with either the Resolver implementation or the configuration.
mutation { addUserToGroup(input: { userId: 1, groupId: 1, roleId: 1}) { result { userId, groupId, roleId }}}
//GraphQL Mutation
public class Mutation
{
public Mutation(IConfiguration configuration) { }
[UseDbContext(typeof(AppDbContext))]
public async Task<AddUserToGroupPayload> AddUserToGroupAsync(AddUserToGroupInput input,
[ScopedService] AppDbContext context
)
{
int someLimit = 5;
var roleCapacity = context.GroupRoleCapacities.First(grc =>
grc.GroupId == input.GroupId &&
grc.RoleId == input.RoleId);
if(roleCapacity.CapacityUsed > someLimit) {
throw ApplicationException("limit exceeded");
}
//add user to group and save etc
return new AddUserToGroupPayload(result);
}
}
Difference here that Hot Chocolate is used only for GraphQL querying, but your code calls EF Core context explicitly. You need some common part.
What you can do:
Better expose DTO instead of database entity
Write common query which returns this DTO
Feed Hot Chocolate with this query. In this case you do not need resolvers at all (if I understand this library correctly)
Sample query (without DTO, never used Hot Chocolate and not familiar with it's attributes)
public static class MyQueries
{
public static IQueryable<GroupRoleCapacity> GetGroupRoleCapacity(AppDbContext context)
{
return context.GroupRoleCapacities.Select(c => new GroupRoleCapacity
{
GroupId = c.GroupId,
Group = c.Group, // I assume you have defined navigation properties
Role = c.Role,
RoleId = c.RoleId,
CapacityUsed = await context.Users.Count(u => u.GroupId == c.GroupId &&
u.UserRoles.Any(ur => ur.RoleId == c.RoleId))
);
}
}
Query:
//GraphQL Query
public class Query
{
[UseDbContext(typeof(AppDbContext))]
[GraphQLType(typeof(NonNullType<ListType<NonNullType<GroupRoleCapacities.GroupRoleCapacityType>>>))]
public IQueryable<GroupRoleCapacity> GetGroupRoleCapacity([ScopedService] AppDbContext context)
{
return MyQueries.GetGroupRoleCapacity(context);
}
}
Mutation:
//GraphQL Mutation
public class Mutation
{
public Mutation(IConfiguration configuration) { }
[UseDbContext(typeof(AppDbContext))]
public async Task<AddUserToGroupPayload> AddUserToGroupAsync(AddUserToGroupInput input,
[ScopedService] AppDbContext context
)
{
int someLimit = 5;
var roleCapacity = MyQueries.GetGroupRoleCapacity(context)
.FirstAsync(grc =>
grc.GroupId == input.GroupId &&
grc.RoleId == input.RoleId);
if (roleCapacity.CapacityUsed > someLimit)
{
throw ApplicationException("limit exceeded");
}
//add user to group and save etc
return new AddUserToGroupPayload(result);
}
}

ASP.Net MVC 3 System.Data.SqlClient.SqlException Timeout expired

I am developing an ASP.Net MVC 3 web application using Entity Framework 4.1. I recently uploaded the application to my test server and I have noticed an error email delivered by ELMAH stating
System.Data.SqlClient.SqlException Timeout expired. The timeout period
elapsed prior to completion of the operation or the server is not
responding.
Below shows some of my code.
Controller
public ActionResult VerifyEmail(int uid, string vid)
{
var userList = _userService.VerifyEmail(uid,vid).ToList();
}
Service
public IList<User> VerifyEmail(int uid, string emailvcode)
{
return _uow.User.Get(u => u.userID == uid && u.emailVerificationCode == emailvcode).ToList();
}
Unit of Work
public class UnitOfWork : IUnitOfWork, IDisposable
{
readonly LocumEntities _context = new LocumEntities();
private GenericRepository<User> _user = null;
public IGenericRepository<User> User
{
get
{
if (_user == null)
{
_user = new GenericRepository<User>(_context);
}
return _user;
}
}
}
Generic Repository
public IList<TEntity> Get(Expression<Func<TEntity, bool>> filter = null,Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
The Timeout error is sometimes happening when the line within the Service method is trying to execute
return _uow.User.Get(u => u.userID == uid && u.emailVerificationCode == emailvcode).ToList();
This error is not happening every time, only occasionally, however, I don't understand why as this query will either return a list of Users, or, a NULL list.
Can anyone spot from my code why this may be happening?
Any feedback would be appreciated as I have no idea why this is happening.
Thanks.
Try increasing the timeout property in the connection string. Also run the SQL Server Profiler to see how much SQL is being generated for your queries, as the query may be returning a large volume of data causing the timeout.

how to unit test controller when automapper is used?

here's my controller
[POST("signup")]
public virtual ActionResult Signup(UserRegisterViewModel user)
{
if (ModelState.IsValid)
{
var newUser = Mapper.Map<UserRegisterViewModel, User>(user);
var confirmation = _userService.AddUser(newUser);
if (confirmation.WasSuccessful)
return RedirectToAction(MVC.Home.Index());
else
ModelState.AddModelError("Email", confirmation.Message);
}
return View(user);
}
here's my unit test:
[Test]
public void Signup_Action_When_The_User_Model_Is_Valid_Returns_RedirectToRouteResult()
{
// Arrange
const string expectedRouteName = "~/Views/Home/Index.cshtml";
var registeredUser = new UserRegisterViewModel { Email = "newuser#test.com", Password = "123456789".Hash()};
var confirmation = new ActionConfirmation<User>
{
WasSuccessful = true,
Message = "",
Value = new User()
};
_userService.Setup(r => r.AddUser(new User())).Returns(confirmation);
_accountController = new AccountController(_userService.Object);
// Act
var result = _accountController.Signup(registeredUser) as RedirectToRouteResult;
// Assert
Assert.IsNotNull(result, "Should have returned a RedirectToRouteResult");
Assert.AreEqual(expectedRouteName, result.RouteName, "Route name should be {0}", expectedRouteName);
}
Unit test failed right here.
var result = _accountController.Signup(registeredUser) as RedirectToRouteResult;
when I debug my unit test, I got following error message: "Missing type map configuration or unsupported mapping."
I think its because configuration is in web project, not the unit test project. what should I do to fix it?
You need to have the mapper configured, so in your test class set up, not the per-test setup, call the code to set up the mappings. Note, you'll also probably need to modify your expectation for the user service call as the arguments won't match, i.e, they are different objects. Probably you want a test that checks if the properties of the object match those of the model being passed to the method.
You should really use an interface for the mapping engine so that you can mock it rather than using AutoMapper otherwise it is an integration test not a unit test.
AutoMapper has an interface called IMappingEngine that you can inject into your controller using your IoC container like below (this example is using StructureMap).
class MyRegistry : Registry
{
public MyRegistry()
{
For<IMyRepository>().Use<MyRepository>();
For<ILogger>().Use<Logger>();
Mapper.AddProfile(new AutoMapperProfile());
For<IMappingEngine>().Use(() => Mapper.Engine);
}
}
You will then be able to use dependency injection to inject AutoMapper's mapping engine into your controller, allowing you to reference your mappings like below:
[POST("signup")]
public virtual ActionResult Signup(UserRegisterViewModel user)
{
if (ModelState.IsValid)
{
var newUser = this.mappingEngine.Map<UserRegisterViewModel, User>(user);
var confirmation = _userService.AddUser(newUser);
if (confirmation.WasSuccessful)
return RedirectToAction(MVC.Home.Index());
else
ModelState.AddModelError("Email", confirmation.Message);
}
return View(user);
}
You can read more about this here: How to inject AutoMapper IMappingEngine with StructureMap
Probably it is cool to abstract mapping into MappingEngine.
Sometimes I use following approach to IOC Automapper
In IOC builder:
builder.RegisterInstance(AutoMapperConfiguration.GetAutoMapper()).As<IMapper>();
where GetAutoMapper is:
public class AutoMapperConfiguration
{
public static IMapper GetAutoMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<OrderModelMapperProfile>();
cfg.AddProfile<OtherModelMapperProfile>();
//etc;
});
var mapper = config.CreateMapper();
return mapper;
}
}
And finally in Controller ctor
public MyController(IMapper mapper)
{
_mapper = mapper;
}

LINQ-To-Sharepoint Multiple content types for a single list

I'm using SPMetal in order to generate entity classes for my sharepoint site and I'm not exactly sure what the best practice is to use when there are multiple content types for a single list. For instance I have a task list that contains 2 content types and I'm defining them via the config file for SPMetal. Here is my definition...
<List Member="Tasks" Name="Tasks">
<ContentType Class="LegalReview" Name="LegalReviewContent"/>
<ContentType Class="Approval" Name="ApprovalContent"/>
</List>
This seems to work pretty well in that the generated objects do inherit from WorkflowTask but the generated type for the data context is a List of WorkflowTask. So when I do a query I get back a WorkflowTask object instead of a LegalReview or Approval object. How do I make it return an object of the correct type?
[Microsoft.SharePoint.Linq.ListAttribute(Name="Tasks")]
public Microsoft.SharePoint.Linq.EntityList<WorkflowTask> Tasks {
get {
return this.GetList<WorkflowTask>("Tasks");
}
}
UPDATE
Thanks for getting back to me. I'm not sure how I recreate the type based on the SPListItem and would appreciate any feedback.
ContractManagementDataContext context = new ContractManagementDataContext(_url);
WorkflowTask task = context.Tasks.FirstOrDefault(t => t.Id ==5);
Approval a = new Approval(task.item);
public partial class Approval{
public Approval(SPListItem item){
//Set all properties here for workflowtask and approval type?
//Wouldn't there be issues since it isn't attached to the datacontext?
}
public String SomeProperty{
get{ //get from list item};
set{ //set to list item};
}
Linq2SharePoint will always return an object of the first common base ContentType for all the ContentTypes in the list. This is not only because a base type of some description must be used to combine the different ContentTypes in code but also it will then only map the fields that should definitely exist on all ContentTypes in the list. It is however possible to get access to the underlying SPListItem returned by L2SP and thus from that determine the ContentType and down cast the item.
As part of a custom repository layer that is generated from T4 templates we have a partial addition to the Item class generated by SPMetal which implements ICustomMapping to get the data not usually available on the L2SP entities. A simplified version is below which just gets the ContentType and ModifiedDate to show the methodology; though the full class we use also maps Modified By, Created Date/By, Attachments, Version, Path etc, the principle is the same for all.
public partial class Item : ICustomMapping
{
private SPListItem _SPListItem;
public SPListItem SPListItem
{
get { return _SPListItem; }
set { _SPListItem = value; }
}
public string ContentTypeId { get; internal set; }
public DateTime Modified { get; internal set; }
public virtual void MapFrom(object listItem)
{
SPListItem item = (SPListItem)listItem;
this.SPListItem = item;
this.ContentTypeId = item.ContentTypeId.ToString();
this.Modified = (DateTime)item["Modified"];
}
public virtual void MapTo(object listItem)
{
SPListItem item = (SPListItem)listItem;
item["Modified"] = this.Modified == DateTime.MinValue ? this.Modified = DateTime.Now : this.Modified;
}
public virtual void Resolve(RefreshMode mode, object originalListItem, object databaseObject)
{
SPListItem originalItem = (SPListItem)originalListItem;
SPListItem databaseItem = (SPListItem)databaseObject;
DateTime originalModifiedValue = (DateTime)originalItem["Modified"];
DateTime dbModifiedValue = (DateTime)databaseItem["Modified"];
string originalContentTypeIdValue = originalItem.ContentTypeId.ToString();
string dbContentTypeIdValue = databaseItem.ContentTypeId.ToString();
switch(mode)
{
case RefreshMode.OverwriteCurrentValues:
this.Modified = dbModifiedValue;
this.ContentTypeId = dbContentTypeIdValue;
break;
case RefreshMode.KeepCurrentValues:
databaseItem["Modified"] = this.Modified;
break;
case RefreshMode.KeepChanges:
if (this.Modified != originalModifiedValue)
{
databaseItem["Modified"] = this.Modified;
}
else if (this.Modified == originalModifiedValue && this.Modified != dbModifiedValue)
{
this.Modified = dbModifiedValue;
}
if (this.ContentTypeId != originalContentTypeIdValue)
{
throw new InvalidOperationException("You cannot change the ContentTypeId directly");
}
else if (this.ContentTypeId == originalContentTypeIdValue && this.ContentTypeId != dbContentTypeIdValue)
{
this.ContentTypeId = dbContentTypeIdValue;
}
break;
}
}
}
Once you have the ContentType and the underlying SPListItem available on your L2SP entity it is simply a matter of writing a method which returns an instance of the derived ContentType entity from a combination of the values of the base type and the extra data for the missing fields from the SPListItem.
UPDATE: I don't actually have an example converter class as we don't use the above mapping extension to Item in this way. However I could imagine something like this would work:
public static class EntityConverter
{
public static Approval ToApproval(WorkflowTask wft)
{
Approval a = new Approval();
a.SomePropertyOnWorkflowTask = wft.SomePropertyOnWorkflowTask;
a.SomePropertyOnApproval = wft.SPListItem["field-name"];
return a;
}
}
Or you could put a method on a partial instance of WorkflowTask to return an Approval object.
public partial class WorkflowTask
{
public Approval ToApproval()
{
Approval a = new Approval();
a.SomePropertyOnWorkflowTask = this.SomePropertyOnWorkflowTask;
a.SomePropertyOnApproval = this.SPListItem["field-name"];
return a;
}
public LegalReview ToLegalReview()
{
// Create and return LegalReview as for Approval
}
}
In either situation you would need to determine the method to call to get the derived type from the ContentTypeId property of the WorkflowTask. This is the sort of code I would normally want to generate in one form or another as it will be pretty repetitive but that is a bit off-topic.

asp.net mvc ObjectDisposedException with ef

I need some help with this error "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection."
It's a asp.net mvc3, EF4, and ms sql.
Here is the razor with two dropdowns:
<div class="editRow">
#Html.DropDownListFor(m=>m.IndustryId, (SelectList)ViewBag.Industry, #Empower.Resource.General.ddlDefaultVal, new { #class = "ddl400" })
#Html.ValidationMessageFor(m => m.IndustryId)
</div>
<div class="editRow">
#Html.DropDownListFor(m=>m.ProvinceId, (SelectList)ViewBag.Province, #Empower.Resource.General.ddlDefaultVal, new {#class = "ddl400"})
#Html.ValidationMessageFor(m => m.ProvinceId)
</div>
Controller:
IndustryService indService = new IndustryService();
ViewBag.Industry = new SelectList(indService.GetAllIndustry(), "IndustryId", "IndustryName");
ProvinceService proService = new ProvinceService();
ViewBag.Province = new SelectList(proService.GetAllProvince(), "ProvinceId", "ProvinceName");
return View();
ProvinceService:
public IEnumerable<Province> GetAllProvince()
{
using (var context = DBContext.ObjectContext)
{
var pros = context.Provinces;
return pros;
}
}
IndustryService is identical as above...
public class DBContext
{
private static EmpowerDBEntities _empowerContext;
public static EmpowerDBEntities ObjectContext
{
get
{
if (_empowerContext == null)
_empowerContext = new EmpowerDBEntities();
return _empowerContext;
}
}
}
I know the problem occurs in second dropdown when it tries to retrive data while the connection is desposed by previous query. Please help me with this, thanks.
The fix is simple - convert to a .ToList() or First() before using. LINQ has deferred execution and tries to run this command after the context is disposed (when your object results are referenced) - not when you actually make the call.. You need to force it to run now while the context is in scope.
using (var context = DBContext.ObjectContext)
{
var pros = context.Provinces;
return pros.ToList();
}
Also - your code above is checking for null in the get accessor. However this object won't be null - it will be disposed, so you cannot do your check this way, you need to check if its null and not disposed.
public class DBContext
{
private static EmpowerDBEntities _empowerContext;
public static EmpowerDBEntities ObjectContext
{
get
{
if (_empowerContext == null || _empowerContext.IsDisposed())
_empowerContext = new EmpowerDBEntities();
return _empowerContext;
}
}
}
something like that anyways :)
I ran into a similar problem. I had been following this pattern, which I had seen in many code examples on the web:
public ActionResult Show(int id)
{
using (var db = new MyDbContext())
{
var thing = db.Things.Find(id);
return View(thing);
}
}
However, this was causing the ObjectDisposedException listed above whenever I tried to access anything that hadn't been loaded into memory in the View code (in particular, one-to-many relationships in the main view model).
I found a different pattern in this example:
public class MyController : Controller
{
private MyDbContext _db;
public MyController()
{
_db = new MyDbContext();
}
public ActionResult Show(int id)
{
// Do work such as...
var thing = _db.Things.Find(id);
return View(thing);
}
protected override void Dispose(bool disposing)
{
_db.Dispose();
base.Dispose(disposing);
}
}
This pattern keeps the database connection alive until the View has finished rendering, and neatly disposes of it when the Controller itself is disposed.
Here is the problem when you are using
using(var context=new CustomerContext())
{
return View(context.Customers.ToList());
}
when the block of code executes all references are disposed that you are lazzy loading so that's why it is throwing this error.
so i used
return View(context.Customers.ToList()) directly it will work perfectly fine.

Resources