Looking for way to do $orderby on a one to many navigation property using OData v4 under WebApi2. As an example, a classic Contact and Address scenario where there is a one to many.
public class Contact
{
public int ContactId { get; set; }
public String FirstName { get; set; }
public virtual ICollection<ContactAddress> Addresses { get; set; }
}
public class ContactAddress
{
public int ContactAddressId { get; set; }
public int ContactId { get; set; }
public virtual Contact Contact { get; set; }
public String AddressOne { get; set; }
public String StateCode { get; set; }
public bool IsPreferred { get; set; }
public String Type { get; set; }
public bool IsActive { get; set; }
}
Where we want to support composing queries that might include ordering by the preferred address StateCode, etc.
I realize that the following won't work as it doesn't terminate in a single value / object:
http://localhost:49380/odata/ODataContacts?$filter=LastName eq 'Smith'&$expand=Addresses&$orderby=Addresses/StateCode
But what I am trying to understand is what the options are to deal with this. It is such a fundamental scenario. In straight LINQ world it is pretty easy to build an expression that will do the appropriate sub filtering on Addresses to allow this. But would like to avoid special case as really trying to just support simple query composition through the OData URL / filter and orderby conventions.
What would be ideal would be something like:
http://localhost:49380/odata/ODataContacts?$filter=LastName eq 'Smith'&$expand=Addresses&$orderby=Addresses($filter=IsPreferred eq true)/StateCode
But this doesn't seem to be supported, at least in OData4 as it is in WebApi.
I have pondered a custom function, and per spec this should be supported in an $orderby expression. But have stumbled on how to implement this. The assumption would be something like:
http://localhost:49380/odata/ODataContacts?$filter=LastName eq 'Smith'&$expand=Addresses&$orderby=ContactService.PreferredAddress(ContactId=$it.ContactId)/StateCode
I have not been successful with this, and it strikes me this will all be "too late" to build the query expression for server side execution regardless, as it is really just a call to another endpoint.
I also stumbled upon some of the ModelBinding functions, many of which take lambda expressions, which seems promising. So for example:
builder.EntitySet<Contact>("ODataContacts")
.HasOptionalBinding<ContactAddress>(c => c.Addresses.FirstOrDefault(a => a.IsPreferred), "PreferredAddress");
Only I am not clear on what I then need to do on actual Entity or Controller to make this all work. I am getting a runtime exception as PreferredAddress doesn't really exist anywhere. But it would seem this is the approach that might work the best?
I can probably get things to work with a DTO/Queryable approach but this seems really unfortunate and a lot of extra work.
Is there a practical approach to this problem???
Related
To skip pain, go to EDIT below.
I'm using EF with AutoMapper IQueryableExtensions.
Two of my models are as follows:
public class Article
{
public int Key { get; set; }
public DateTimeOffset Created { get; set; }
public int? SemesterKey { get; set; }
public virtual Semester Semester { get; set; }
}
public class Semester
{
public int Key { get; set; }
public string Name { get; set; }
public virtual ICollection<Article> Articles { get; set; }
// Other relationships
public virtual ICollection<Subject> Subjects { get; set; }
public virtual ICollection<AppUser> Users { get; set; }
}
And I have the following DTOs:
public class ArticleDto
{
public int Key { get; set; }
public DateTimeOffset Created { get; set; }
// If I remove this (or ignore it), everything works.
public SemesterDto Semester { get; set; }
}
public class SemesterDto
{
public int Key { get; set; }
public string Name { get; set; }
}
Configuration:
Mapper.CreateMap<Semester, SemesterDto>().MaxDepth(1);
Mapper.CreateMap<Article, ArticleDto>().MaxDepth(1);
The query:
context.Articles.Include(a => a.Semester)
.OrderByDescending(a => a.Created)
.Take(5)
.ProjectTo<ArticleDto>()
.ToList();
Executing the query yields the following strange exception:
System.ArgumentException: Property 'NS.Models.Semester Semester' is not defined for type 'NS.Models.Semester'
This is some of the stack trace:
System.Linq.Expressions.Expression.Property(Expression expression, PropertyInfo property)
System.Linq.Expressions.Expression.MakeMemberAccess(Expression expression, MemberInfo member)
....
AutoMapper.MappingEngine.CreateMapExpression(ExpressionRequest request, Expression instanceParameter, IDictionary`2 typePairCount)
It seems as if AutoMapper generated an expression that is looking for a Semester property (in response to ArticleDto.Semester it seems) on the NS.Models.Semester type which of course doesn't exist.
I can get it working if I do the following:
Mapper.CreateMap<Article, ArticleDto>().MaxDepth(1)
.ForMember(a => a.Semester, c => c.MapFrom(a => a.Semester == null ? null : new SemesterDto() {
Key = a.Semester.Key,
Year = a.Semester.Year }));
But this is just the thing I'm trying to avoid writing by using AutoMapper!
It's probably something wrong on my side but I can't find anything suspicious.
EDIT:
I have the following ctors on SemesterDto:
public SemesterDto()
{
}
public SemesterDto(int key, string name)
{
Key = key;
Name = name;
}
When I remove the second one everything works. Seems this is it, this is really strange behavior though. I never thought the ctor would make a problem so I didn't include it for clarity, everything is possible isn't it. Sorry about that.
So, is this a bug from AutoMapper or is there something else I misunderstood?
EDIT 2:
Stripping this more, I tried mapping a Semester to SemesterDto directly and this is the result:
context.Semesters.ProjectTo<SemesterDto>().FirstOrDefault();
NotSupportedException: Only parameterless constructors and initializers are supported in LINQ to Entities.
This strengthens the idea that the ctor is causing strange behavior.
I can reproduce the issue in your second edit. Somehow (I don't know AutoMapper well enough to see why), the parametrized constructor always takes precedence over the parameterless one in AutoMapper. And EF doesn't support parametrized constructors in its expression tree.
Anyway, you can fix this issue by using ConstructProjectionUsing:
Mapper.CreateMap<Semester, SemesterDto>().MaxDepth(1)
.ConstructProjectionUsing(sem => new SemesterDto());
This has been confirmed as a bug, see this github issue. For now, I guess I'll just avoid ctors in my DTOs until the promised next release.
If you have v4.1.0 or above, then this should've been fixed.
Mapper.CreateMap<Article, Dto.Article>()
.ForMember(d => d.Semester, o => o.MapFrom(s => Mapper.Map<Dto.Semester>(s));
You need to tell it to use the Semester -> Dto.Semester mapping.
I've googled some around the internet and found some articles about the subject, but none of them satisfied me. I want to know is it good to use object-object mapper to map objects to each other? I know it depends on situation to use, but how will I realize a good or best situation to use?
Taking a step back, it's best practice to separate data transfer objects (DTOs) and view models (VMs) from business objects (entities and alike). Mapping libraries, in that regard, are a means to an end and simply make that association easier.
As far as when, that's up to you. If you feel like you can convert between your business models and DTO/VMs in a way that's easy to maintain, go ahead. From my personal experience, that only goes so far (especially as the requirements change). Therefore, I'm fond of mapping libraries (specifically AutoMapper as I've come to know it's API and am comfortable plugging it in).
Having said that, any time I have to go between these two models I use AutoMapper. I simply configure it once and I'm off and running. Additional tweaks to the models (on either side) then become easier as I can change those bindings in one place (map definition file) and methods automatically "catch up".
Example:
My database contains a Record for a product:
class Product
{
public int Id { get; set; }
public string Description { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public int QuantityOnHand { get; set; }
public int? ReorderQuantity { get; set; }
public string Sku { get; set; }
}
I may present this to the UI in a more distilled format:
public class ProductViewModel
{
public int Id { get; set; }
public string Description { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public int Quantity { get; set; }
}
If this came from a repository of some kind, I'm simply calling:
var model = Mapper.Map<ProductViewModel>(productFromRepository)
Here I consistently get the view model I care about from the Product I've requested. If the business/service layer were to add/change/remove properties, I'd only go back to my Mapper.CreateMap<Product, ProductViewModel>() defintion, but the rest of my presentation logic would remain in-tact.
In addition to #Brad Christie's answer, automapping types which have minor differences into a single overarching type is generally easier if you are meaning to display them on your view alongside other products that are generated different ways.
If you'll allow me to crib off one my own previous answers, here's an example:
class SingleProduct {
string Name {get;set;}
decimal Price {get;set;}
decimal GetActualPrice() { return Price; }
}
class ComboSaleProduct {
string Name {get;set;}
List<SingleProduct> ComboProducts {get;set;}
decimal GetActualPrice() { return ComboProducts.Sum(p => p.GetActualPrice()); }
}
class ProductViewModel {
string Name {get;set;}
decimal ActualPrice {get;set;}
}
Automapper wires everything together so that you can return either of these and it will automatically map the "GetActualPrice" to ActualPrice on your viewmodel.
I'm new in Asp.net MVC pattern. I used to develop website using asp.net web forms. I did research for hours about MVC and got a little understanding. But there're still some problems that keep confusing me.
(I decided to use Code First)
I have to display some pretty complex data, for my old project, i was using Stored Procedure, now i think i'm gonna change to LINQ, the questions are :
Does it affect the performance ? I was having good performance with SP.
How do i "define" the objects created by the query (in this case is the result set), should i define it in model classes ?
To be more specific, this is how i get things done in my old project, i have a class called ReportData (please note this is a mini-version, the actual class contains more properties) :
public class ReportData
{
public string CityID { set; get; }
public DateTime ResultDate { set; get; }
/// ...
// free fields
public int INT1 { set; get; }
public int INT2 { set; get; }
public int INT3 { set; get; }
public int INT4 { set; get; }
public string STR1{ set; get; }
public string STR2 { set; get; }
public string STR3 { set; get; }
public string STR4 { set; get; }
}
And everytime a Stored Procedure is executed, i put the result into this class, and build the HTML markup from this class :
foreach (ReportData r in listReportData)
{
// build html markup here
}
This way i can save a whole lot of works, i don't have to write code twice for any "same same" results. And it's running ok, but i have to change due to some circumstances.
Now when stepping into MVC world, i found it very confused and honestly i'm now lost with the controlers, models, and views stuffs.
I also read about some related topic which pops up when i was typing this question, but they seem doesn't help me much in my case, oh i'm too nerd to understand the answers!
I have an MVC app using EF code first. I add a user to the system and enter pension details, part of this is a dropdown linked to a model called PensionBenefitLevel. This is the model -
[Key]
public int PensionBenefitLevelID { get; set; }
public string DisplayText { get; set; }
public int EmployeePercentage { get; set; }
public int EmployerPercentage { get; set; }
public virtual ICollection<Pension> Pension { get; set; }
When registered I have the PensionBenefitLevelID that came from the dropdown, but in my controller I was to peform a calculation using the EmployerPercentage value that is related to that ID. Can anyone point me in the correct direction?
Do I need to create a variable in the controller and use a linq query to get that value back? I've not been able to find any examples of something similar so if you could point me to one that would be great too.
If I understand the question correctly, you want to get back the entity corresponding to PensionBenefitLevelID and perform a calculation on the EmployerPercentage field.
Since you haven't mentioned what pattern you are using with EF (repository, unit of work, etc.) I can only give you a general answer:
var entity = [Your DB Context].[Your Entity].GetById(pensionBenefitLevelID);
if(entity != null)
{
[Calculation]
}
Say I have a simple model like these (small part of a pretty large app)
public class EHR : IEntity
{
public int ID { get; set; }
public string UserName { get; set; }
public DateTime CreationDate { get; set; }
public virtual ICollection<PhysicalTest> PhysicalTests { get; set; }
}
public class PhysicalTest : IEntity
{
public int ID { get; set; }
public virtual EHR Ehr { get; set; }
public Boolean IsDeleted { get; set; }
}
And i want for an easy way to get the physicalTests that are NOT deleted for a given EHR.
So, I can think of three ways of doing this.
one is simply adding a method to my EHR class.(it doesnt seem as such a bad idea cause I dont want to suffer from anemic domain model)
public IEnumerable<PhysicalTest> ActivePhysicalTests()
{
return this.PhysicalTests.Where(!m=>m.IsDeleted).ToList();
}
the other one is creating an extension method under a EHRRepositoryExtensions class:
public static class EHRRepositoryExtensions
{
public static IEnumerable<PhysicalTest> Active(this IEnumerable<PhysicalTest> physicalTests)
{
return physicalTests.Where(test => !test.IsDeleted).OrderByDescending(test => test.CreationDate).ToList();
}
}
I also think I could have extended my IRepository to include a method that returns only the physsicalTests that arent deleted.
something like
public class EHRRepository : IRepository<EHR>
{
//TODO: method that returns only the physsicalTests that arent deleted.
}
I am still trying to grasp many concepts on DDD and I want it to be as pure as possible.
Which of this approaches would you recommend?
whats a rule of thumb on topics like this?
Please Help.
The first approach is recommended as EHR is your Aggregate Root and it is the information expert about its physical tests.
Second approach is not relevant as you have already the model and you can add this method to the entity instead.
The third approach would be preferable only if the list of physical tests takes much time to load from the database, still you can utilize lazy loading but if you want to separate the fetching from the domain or you dont use a lazy loading enabled ORM then put it as a query method in the repository