According to this documentation page there is a neat way to count nested collection elements without actually fetching them. Great. But the example is for a single item.
Of course, I could iterate and do this in post-processing. But that's not neat.
And here could something like Automapper come into play. It has a ProjectTo extension for IQueryable which can be parameterized (in quite a weird way imho).
Well, I can't get it working. Let's say I have a Client entity that has associated Device entities, and I want to set the DeviceCount property of the target.
By combining the examples from the two sources, something like this could work:
_configuration = new MapperConfiguration(cfg =>
{
SMPContext ctx = null; ///why not an extra property in conf?
cfg.CreateProjection<Client, Models.ViewModels.Client>()
.ForMember(d => d.DeviceCount, conf => conf.MapFrom(s => ctx.Entry(s).Collection(c => c.Devices).Query().Count()));
});
}
...
public async Task<IActionResult> Index()
{
var res = await
_context
.Clients.ProjectTo<SMP.Models.ViewModels.Client>(_configuration, new { ctx = _context })
.ToListAsync();
...
I was experimenting with this too:
public async Task<IActionResult> Index()
{
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateProjection<Client, Models.ViewModels.Client>()
.ForMember(d => d.DeviceCount, conf => conf.MapFrom(s => _context.Entry(s).Collection(c => c.Devices).Query().Count()));
});
var res = await
_context
.Clients.ProjectTo<SMP.Models.ViewModels.Client>(configuration)
.ToListAsync();
...
Either way, I get such an exception, which is quite understandable:
InvalidOperationException: The LINQ expression
'___context_0.Entry(NavigationTreeExpression Value:
EntityReference: Client Expression: c).Collection(c =>
c.Devices).Query() .Count()' could not be translated.
I have tried using Map instead of ProjectTo, but no luck either, as IMapper.Map().AfterMap passes the entire enumerable as parameter instead of per item.
Is there any neat solution?
It looks like there is a simple and neat solution with AutoMapper after all: CreateProjection is smart enough to create a performant SQL mapping in such a case:
builder.Services.AddAutoMapper(config =>
config.CreateProjection<SMP.Models.DataModels.Client, SMP.Models.ViewModels.Client>()
.ForMember(d => d.DeviceCount, o=> o.MapFrom(s => s.Devices.Count))
);
Using it is simple as well:
var res = await _context.Clients
.ProjectTo<Models.ViewModels.Client>(_mapper/*injected IMapper*/.ConfigurationProvider).ToListAsync();
The generated SQL query looks like this:
Related
I'm having an issue with EFCore 2.0 where its impossible to properly delete dependent entities when updating the prinicipal entity. Here's my code:
Mapping
public class BlueprintMap : IEntityTypeConfiguration<Blueprint>
{
public void Configure(EntityTypeBuilder<Blueprint> builder)
{
builder.ToTable("blueprint").HasKey(t => t.Id);
builder.Property(x => x.Id).HasColumnName("blueprint_id").ValueGeneratedOnAdd();
builder.HasMany<Objective>().WithOne(x => x.Blueprint).OnDelete(DeleteBehavior.Cascade);
}
}
public class ObjectiveMap : IEntityTypeConfiguration<Objective>
{
public void Configure(EntityTypeBuilder<Objective> builder)
{
builder.ToTable("objective").HasKey(x => x.Id);
builder.Property(x => x.Id).HasColumnName("objective_id").ValueGeneratedOnAdd();
builder.HasOne(x => x.Blueprint).WithMany(y => y.Objectives).HasForeignKey("blueprint_id");
}
}
Update WebApi Call
Get from context
var blueprint = await blueprintContext.Blueprints
.Where(x => id == x.Id)
.Include(x => x.Objectives)
.SingleOrDefaultAsync();
Remove objectives from blueprint entity (Objectives is ICollection, the dependent entity
blueprint.Objectives.Remove(objective);
// Get a new context
var blueprintContext = await _blueprintContextFactory.CreateContext();
Blueprint entity AND Blueprint from context have 0 objectives
int x = await blueprintContext.SaveChangesAsync();
Get WebApi Call
Get from context
return await blueprintContext.Blueprints
.Where(x => id == x.Id)
.Include(x => x.Objectives)
.SingleOrDefaultAsync();
blueprint has 2 objectives again.
Stupid mistake. The first context tracks the objectives being removed. When a new context is generated to save the changes, it doesn't know about the objectives being removed so they remain and regenerate. In other words, the Update request is using multiple contexts instead of just 1 per request and this is unacceptable by EF Core's design.
I'm trying to use Teleric's Kendo Grid in ASP.NET MVC 5. I'm following the example here, but the grid is not populating with data. The columns show up, but it says "No items to display".
http://docs.telerik.com/kendo-ui/getting-started/using-kendo-with/aspnet-mvc/helpers/grid/ajax-binding
This is my Words/Read function:
public ActionResult Read([DataSourceRequest] DataSourceRequest request)
{
var items = db.Words.Take(1).ToList();
//this code was necessary to get the correct JSON result
JsonSerializerSettings jsSettings = new JsonSerializerSettings();
jsSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
var converted = JsonConvert.SerializeObject(items, null, jsSettings);
return Content(converted);
}
I got the following JSON result from Words/Read:
[{"WordId":1,"Rank":1,"PartOfSpeech":"article","Image":"Upload/29/1/Capture1.PNG","FrequencyNumber":"22038615","Article":null,"ClarificationText":null,"WordName":"the | article","MasterId":0,"SoundFileUrl":"/UploadSound/7fd752a6-97ef-4a99-b324-a160295b8ac4/1/sixty_vocab_click_button.mp3","LangId":1,"CatId":null,"IsActive":false}]
Finally, my view page:
#(Html.Kendo().Grid<NextGen.Models.Word>
()
.Name("grid")
.DataSource(dataSource => dataSource
.Ajax()
.PageSize(15)
.Read(read => read.Action("Read", "Words"))
)
.Columns(columns =>
{
columns.Bound(c => c.WordName);
columns.Bound(c => c.PartOfSpeech);
})
.Pageable()
.Sortable()
)
I've seen some similar questions to this one, but most are using Javascript rather than Html helpers. Am I doing something dumb?
I found an alternative way to get around the circular reference problem I was having. Basically, the answer is here: A circular reference was detected while serializing an object of type 'SubSonic.Schema .DatabaseColumn'.
For people with the same problem: I took the two properties I needed - WordName and PartOfSpeech, and passed only those attributes to the toDataSource function Kendo provides.
My new read function looks like this:
public ActionResult Read([DataSourceRequest] DataSourceRequest request)
{
var items = db.Words.Select(w => new WordL{
WordName = w.WordName,
PartOfSpeech = w.PartOfSpeech
});
return Json(items.ToDataSourceResult(request));
}
where WordL is a model with just PartOfSpeech and WordName attributes, rather than all the attributes of my class.
It is probably because you are not using 2 methods in your controller. While your page view is first ActionResult you should use second one as in the grid as you did in your example. I hope it is obvious.
public ActionResult ReadPage()
{
return View();
}
public ActionResult Read([DataSourceRequest] DataSourceRequest request)
{
var items = db.Words.Take(1).ToList();
//this code was necessary to get the correct JSON result
JsonSerializerSettings jsSettings = new JsonSerializerSettings();
jsSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
var converted = JsonConvert.SerializeObject(items, null, jsSettings);
return Content(converted);
}
i followed all of these steps in this tutorial:
Created a validator class
public class ProjectValidator : AbstractValidator<ProjectViewModel>
{
public ProjectValidator()
{
//RuleFor(h => h.Milestone).NotEmpty().WithName("Milestone");
RuleFor(h => h.Applications).NotNull().WithName("Application");
RuleFor(h => h.InitiativeName).NotNull().WithName("Business Aligned Priority");
RuleFor(h => h.BusinessDriverId).NotNull().WithName("Business Driver");
RuleFor(h => h.FundingTypeId).NotNull().WithName("Funding Type");
RuleFor(h => h.Description).NotEmpty().WithName("Description");
RuleFor(h => h.Name).NotEmpty().WithName("Project Name");
RuleFor(h => h.Sponsors).NotNull().WithName("Sponsors");
}
}
Put an attribute on my DTO to specific this validtor
[Validator(typeof(ProjectValidator))]
public class ProjectViewModel
{
}
but after a form post when i go to check the ModelState errors list, the errors i see are coming from the asp.net-mvc default validation.
public ActionResult UpdateMe(ProjectViewModel entity)
{
Project existingProject = this.Repository.Fetch<Project>(entity.Id);
UpdateProperties(entity, existingProject);
var allErrors = ModelState.Values.SelectMany(v => v.Errors);
if (allErrors.Count() > 0)
{
any suggestions on why its not picking up the fluent. validator ?? I have added an image below of what i see on the gui
if i call the validator directly in code it works just fine:
ProjectValidator validator = new ProjectValidator();
ValidationResult result = validator.Validate(entity);
I'm not sure what type of HTML element FundingTypeId is but I'm assuming it is a dropdown list. If nothing is selected then it will give you this error. This is unfortunately one of the limitations of the FV integration with MVC which is caused by the bad design of MVC's default model binder. That message is not generated by FV but rather is produced by the DefaultModelBinder, in this case where the incoming value cannot be converted to the property type.
Check out these 2 questions which I posted on the Fluent Validation discussion forum:
http://fluentvalidation.codeplex.com/discussions/250798
http://fluentvalidation.codeplex.com/discussions/253389
Say I have:
ProductA
ProductB
ProductScreen
ProductAScreen1 : ProductScreen
ProductAScreen2 : ProductScreen
ProductBScreen1 : ProductScreen
ProductBScreen2 : ProductScreen
How can I set it up so that I register the screens locally to the product? So when I am in ProductA and passing IEnumerable it doesn't resolve ProductB's screens?
I thought this would be achievable using something like the lifetime scope, but it doesn't appear I understood it correctly.
Lifetime scope is used to control instance lifetime. You are talking about controlling selection. For that, you should look at the metadata features in Autofac.
Using metadata, you can "tag" each product screen, indicating which product it belongs to:
builder.Register(c => new ProductAScreen1()).As<ProductScreen>()
.WithMetadata<IProductScreenMetadata>(m =>
m.For(am => am.ProductType, typeof(ProductA)));
builder.Register(c => new ProductAScreen2()).As<ProductScreen>()
.WithMetadata<IProductScreenMetadata>(m =>
m.For(am => am.ProductType, typeof(ProductA)));
builder.Register(c => new ProductBScreen1()).As<ProductScreen>()
.WithMetadata<IProductScreenMetadata>(m =>
m.For(am => am.ProductType, typeof(ProductB)));
builder.Register(c => new ProductBScreen2()).As<ProductScreen>()
.WithMetadata<IProductScreenMetadata>(m =>
m.For(am => am.ProductType, typeof(ProductB)));
Next, you can take a dependency on a IEnumerable<Lazy<ProductScreen, IProductScreenMetadata>> and resolve screens according to product type:
var productScreens = _screens.WHere(a => a.Metadata.ProductType == typeof(ProductA));
Update: for completeness, here is a simpler solution using the Keyed approach. First, registration is much simpler:
builder.RegisterType<ProductAScreen1>().Keyed<ProductScreen>(typeof(ProductA));
builder.RegisterType<ProductAScreen2>().Keyed<ProductScreen>(typeof(ProductA));
builder.RegisterType<ProductBScreen1>().Keyed<ProductScreen>(typeof(ProductB));
builder.RegisterType<ProductBScreen2>().Keyed<ProductScreen>(typeof(ProductB));
To resolve a collection of keyed services we'll have to take a dependency on the IIndex<,> type:
public class SomeService
{
private IEnumerable<ProductScreen>> _screens;
public SomeService(IIndex<Type, IEnumerable<ProductScreen>> screens)
{
_screens = screens;
}
public void DoSomething()
{
var screensForProductA = _screens[typeof(ProductA)];
}
}
Note: for the curious: instead of hardcoding the type registrations, here's how you can do a "by convention" registration:
var assembly = ...;
var productTypes = ...; // a collection of all the product types
foreach(var productType in productTypes)
{
builder.RegisterAssemblyTypes(assembly)
.Where(t => typeof(ProductScreen).IsAssignableFrom(t))
.Where(t => t.Name.StartsWith(productType.Name))
.Keyed<ProductScreen>(productType);
}
I'm working on an ASP.NET MVC application, and am trying to write some unit tests against controller actions, some of which manipulate properties on the HttpContext, such as Session, Request.Cookies, Response.Cookies, etc. I'm having some trouble figuring out how to "Arrange, Act, Assert"...I can see Arrange and Assert...but I'm having trouble figuring out how to "Act" on properties of a mocked HttpContextBase when all of its properties only have getters. I can't set anything on my mocked context from within my controller actions...so it doesn't seem very useful. I'm fairly new to mocking, so I'm sure there's something that I'm missing, but it seems logical to me that I should be able to create a mock object that I can use in the context of testing controller actions where I can actually set property values, and then later Assert that they're still what I set them to, or something like that. What am I missing?
public static HttpContextBase GetMockHttpContext()
{
var requestCookies = new Mock<HttpCookieCollection>();
var request = new Mock<HttpRequestBase>();
request.Setup(r => r.Cookies).Returns(requestCookies.Object);
request.Setup(r => r.Url).Returns(new Uri("http://example.org"));
var responseCookies = new Mock<HttpCookieCollection>();
var response = new Mock<HttpResponseBase>();
response.Setup(r => r.Cookies).Returns(responseCookies.Object);
var context = new Mock<HttpContextBase>();
context.Setup(ctx => ctx.Request).Returns(request.Object);
context.Setup(ctx => ctx.Response).Returns(response.Object);
context.Setup(ctx => ctx.Session).Returns(new Mock<HttpSessionStateBase>().Object);
context.Setup(ctx => ctx.Server).Returns(new Mock<HttpServerUtilityBase>().Object);
context.Setup(ctx => ctx.User).Returns(GetMockMembershipUser());
context.Setup(ctx => ctx.User.Identity).Returns(context.Object.User.Identity);
context.Setup(ctx => ctx.Response.Output).Returns(new StringWriter());
return context.Object;
}
Not sure if anyone is interested but I have translated the Moq FakeHttpContext to one using Rhino Mocks (my weapon of choice).
public static HttpContextBase FakeHttpContext()
{
var httpContext = MockRepository.GenerateMock<HttpContextBase>();
var request = MockRepository.GenerateMock<HttpRequestBase>();
var response = MockRepository.GenerateMock<HttpResponseBase>();
var session = MockRepository.GenerateMock<HttpSessionStateBase>();
var server = MockRepository.GenerateMock<HttpServerUtilityBase>();
var cookies = new HttpCookieCollection();
response.Stub(r => r.Cookies).Return(cookies);
request.Stub(r => r.Cookies).Return(cookies);
request.Stub(r => r.Url).Return(new Uri("http://test.com"));
httpContext.Stub(x => x.Server).Return(server);
httpContext.Stub(x => x.Session).Return(session);
httpContext.Stub(x => x.Request).Return(request);
httpContext.Stub(x => x.Response).Return(response);
var writer = new StringWriter();
var wr = new SimpleWorkerRequest("", "", "", "", writer);
System.Web.HttpContext.Current = new System.Web.HttpContext(wr);
return httpContext;
}
with a usage in a Unit Test of
_httpContext = FakeHttpContext();
var cookieManager = new CookieManager(_httpContext);
string username = cookieManager.GetUsername();
_httpContext.AssertWasCalled(hc => { var dummy = hc.Request; });
Hey, I think you're just experiencing a bit of a disconnect here, no big deal. What you describe is 100% possible.
I'm not entirely positive on why you can't set properties on your Mocks, but if you post the full code for your test I'd be happy to go through it with you. Just off the top of my head, I'll suggest two things:
There is a difference between Setup() and SetupProperty(). SetupProperty() is probably what you're after if you want to track values on properties, rather than just get a value from them once.
Alternately try calling SetupAllProperties() on any mock that you need to set a property on.
Check the Moq quickstart as well for some examples.