AutoMapper strange IQueryable projection exception - asp.net-mvc

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.

Related

Cannot create foreign key constraint on self-joining Many-to-Many relationship

I have created the following classes:
public class Character
{
public int ID { get; set; }
public string Title { get; set; }
public ICollection<Relationship> RelatedTo { get; set; }
public ICollection<Relationship> RelatedFrom { get; set; }
}
public class Relationship
{
public int ToID { get; set; }
public int FromID { get; set; }
public Character CharacterFrom { get; set; }
public Character CharacterTo { get; set; }
public string Details { get; set; }
}
In my Context I have this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Relationship>()
.HasKey(r => new { r.ToID, r.FromID });
modelBuilder.Entity<Relationship>()
.HasOne(r => r.CharacterFrom)
.WithMany(r => r.RelatedTo)
.HasForeignKey(r => r.FromID)
.OnDelete(DeleteBehavior.ClientSetNull);
modelBuilder.Entity<Relationship>()
.HasOne(r => r.CharacterTo)
.WithMany(r => r.RelatedFrom)
.HasForeignKey(r => r.ToID)
.OnDelete(DeleteBehavior.ClientSetNull);
}
I think that it is right but I cannot apply the migration due to the following error:
Cannot create the foreign key "FK_Relationship_Character_FromID" with the SET NULL referential action, because one or more referencing columns are not nullable.
I've tried every combination of DeleteBehaviour for OnDelete. None of them work. I don't believe I can make the ICollections nullable and it doesn't seem right that I'd want to. I've spent two hours on this searching for answers. Every tutorial or explanation on EF Core that I've tried to follow seems to take a slightly different approach and be subtly incompatible with every other one. Please help!
The error is telling you that you cannot use DeleteBehavior.ClientSetNull (or DeleteBehavior.SetNull) because the corresponding FK property is not nullable - both ToID and FromID are of type int, hence does not allow setting to null (neither client nor server).
To turn off the cascade delete (in order to break the multiple cascade paths I guess) for required FK relationships, use DeleteBehavior.Restrict instead.

EntityFramework.Extended on EF6 derived types

Consider the following EF entities:
public class Location
{
[Key]
public int LocationId { get; set; }
[StringLength(50)]
[Required]
public string LocationDescription { get; set; }
}
public class LocationHolding : Location
{
[StringLength(50)]
[Required]
public string LocationDescriptionHolding { get; set; }
}
The context is derived from DbContext and has a single DbSet:
public IDbSet<Location> Location { get; set; }
Using the EntityFramework.Extended package, upon attempting a batch update:
context.Location
.OfType<LocationHolding>()
.Where(x => true)
.Update(u => new LocationHolding
{
LocationDescription = u.LocationDescriptionHolding
});
The following exception is thrown:
There are no EntitySets defined for the specified entity type 'TestEfHierarchy.Model.LocationHolding'.
If 'TestEfHierarchy.Model.LocationHolding' is a derived type, use the base type instead.
Parameter name: TEntity
Whilst having success with the package against simple single entities, I've been unable to find an examples where this has been used in more complex situations.
For the entity hierarchy, both table-per-hierarchy and table-per-type have been tried, both with the same result. The only situation I have found to work is when updating with static values (i.e. LocationDescription = "Foo") and the hierarchy is TPH.
Has anybody had similar experiences or found alternative workarounds? Appreciate that alternative methods such as stored procedures are available, but would like to use a code based/fluent approach.

Entity Framework 6 - How to mark a property as NOT foreign key

I'm using ASP.NET MVC5 together with EF6 and using the code first approach.
I have a property in a model that i need to to tell EF6 is NOT a foreign key:
public class LogEntry
{
public int ID { get; set; }
public int LogDayID { get; set; }
public int LogEntryTypeID { get; set; }
public int DepartmentID { get; set; }
public DateTime Clock { get; set; }
public string Text { get; set; }
public virtual LogDay LogDay { get; set; }
public virtual LogEntryType LogEntryType { get; set; }
public virtual Department Department { get; set; }
}
[NotMapped]
public class Department
{
public int ID { get; set; }
public string Title { get; set; }
}
The model Department has the [NotMapped] as this model should not be stored in the database.
I thought this was enough to make EF6 realise that DepartmentID in LogEntry shouldn't be a foreign key.. But instead it throws me an error that 'Department' is not mapped.
EDIT: Even if i remove the DepartmentID from LogEntry it still complains with the above error.
Here's the complete error message:
"The type 'SupervisorLogWeb.Models.Department' was not mapped. Check that the type has not been explicitly excluded by using the Ignore method or NotMappedAttribute data annotation. Verify that the type was defined as a class, is not primitive or generic, and does not inherit from EntityObject."
Apparently your ComplexType is discovered as a Entity - this happens, if you decided to refactor an former Entity to a ComplexType.
The ModelBuilder will decide if an type is an Entity or not (more or less) by it's presence or absence in the DbContext.
So check if your class is still defined as DbSet inside the Context and adjust accordingly.
Add the NotMapped attribute to the DeparmentID property as well. This attribute can also be applied on properties.
When all your mappings are based on conventions, EF (or any tool) can't really tell whether you broke the convention intentionally or you made a mistake. It can apply some heuristics but it's better to fail and ask the programmer than implement an unwanted mapping.

MVC 4, Upshot entities cyclic references

I have a DbDataController which delivers a List of Equipment.
public IQueryable<BettrFit.Models.Equipment> GetEquipment() {
var q= DbContext.EquipmentSet.OrderBy(e => e.Name);
return q;
}
In my scaffolded view everything looks ok.
But the Equipment contains a HashSet member of EquipmentType. I want to show this type in my view and also be able to add data to the EquipmentType collection of Equipment (via a multiselect list).
But if I try to include the "EquipmentType" in my linq query it fails during serialisation.
public IQueryable<BettrFit.Models.Equipment> GetEquipment() {
var q= DbContext.EquipmentSet.Include("EquipmentType").OrderBy(e => e.Name);
return q;
}
"Object Graph for Type EquipmentType Contains Cycles and Cannot be Serialized if Reference Tracking is Disabled"
How can I switch on the "backtracking of references"?
Maybe the problem is that the EquipmentType is back-linking through a HashSet? But I do not .include("EquipmentType.Equipment") in my query. So that should be ok.
How is Upshot generating the model? I only find the EquipmentViewModel.js file but this does not contain any model members.
Here are my model classes:
public class Equipment
{
public Equipment()
{
this.Exercise = new HashSet<Exercise>();
this.EquipmentType = new HashSet<EquipmentType>();
this.UserDetails = new HashSet<UserDetails>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Picture { get; set; }
public string Link { get; set; }
public string Producer { get; set; }
public string Video { get; set; }
public virtual ICollection<EquipmentType> EquipmentType { get; set; }
public virtual ICollection<UserDetails> UserDetails { get; set; }
}
public class EquipmentType
{
public EquipmentType()
{
this.Equipment = new HashSet<Equipment>();
this.UserDetails = new HashSet<UserDetails>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Equipment> Equipment { get; set; }
public virtual ICollection<UserDetails> UserDetails { get; set; }
}
try decorating one of the navigation properties with [IgnoreDataMember]
[IgnoreDataMember]
public virtual ICollection<Equipment> Equipment { get; set; }
The model generated by upshot can be found on the page itself. In your Index view you will see the UpshotContext HTML helper being used (given that you are using the latest SPA version), in which the dataSource and model type are specified.
When the page is then rendered in the browser, this helper code is replaced with the actual model definition. To see that, view the source code of your page in the browser and search for a <script> tag that starts with upshot.dataSources = upshot.dataSources || {};
Check here for more info about how upshot generates the client side model.
As for the "backtracking of references", I don't know :)
I figured out - partially how to solve the circular reference problem.
I just iterated over my queried collection (with Include() ) and set the backreferences to the parent to NULL. That worked for the serialisation issue which otherwise already breaks on the server.
The only problem now is the update of a data entity - its failing because the arrays of the referenced entitycollection are static...
To solve the cyclic backreference, you can use the IgnoreDataMember attribute. Or you can set the back reference to NULL before returning the data from the DbDataController
I posted a working solution to your problem in a different question, but using Entity Framework Code First.
https://stackoverflow.com/a/10010695/1226140
Here I show how to generate your client-side model manually, allowing to you to map the data however you please

ASP.NET-MVC2: Why does TryUpdateModel ignore properties after the second level of the object model tree?

Perhaps I'm missing something here, but it seems that anything in the object model tree 3 or more levels down, is ignored when using TryUpdateModel.
For example (simplified):
public virtual ActionResult SomeAction(int id, FormCollection form)
{
IValueProvider vpFrom = form.ToValueProvider();
/*
At this stage, vpForm contains:
1)PropertyA
2) PropertyB.SubPropertyA
3) PropertyB.SubPropertyB.SubSubPropertyA
*/
TryUpdateModel(someObjectModel, null, null, null, vpFrom);
//The first two properties are applied, number (3) seems to be ignored
Am I missing something here? If this is just the way it is, has anyone come up with a workaround?
A quick project created with the following model.
public class TestModel {
public TestModelA A { get; set; }
public string Name { get; set; }
}
public class TestModelA {
public TestModelB B { get; set; }
public string Name { get; set; }
}
public class TestModelB {
public TestModelC C { get; set; }
public string Name { get; set; }
}
public class TestModelC {
public TestModelD D { get; set; }
public string Name { get; set; }
}
public class TestModelD {
public TestModelE E { get; set; }
public string Name { get; set; }
}
public class TestModelE {
public string Name { get; set; }
}
Here's my edit - which is essentially the same as yours
[HttpPost]
public ActionResult Edit(FormCollection form) {
IValueProvider vpFrom = form.ToValueProvider();
Models.TestModel t = new Models.TestModel();
TryUpdateModel(t, null, null, null, vpFrom);
return View(t);
}
This all works exactly as expected with all the models created properly. The only problem that I can see happening is that you possibly aren't passing the same property names back from the form. (by not using <%: Html.TextBoxFor(model => model.A.B.C.CName)%> for example)
The models require parameterless constructors. But I'm sure you would have gotten an error about that - unless you're consuming the error.
So without more information about your project it will be hard to help as a basic setup produces expected results.
I believe the problem is in one of your model classes. Check, please, if PropertyB.SubPropertyB.SubSubPropertyA is really a property but not a field. A property should have get and set accessors.
Here's my checklist:
Make sure you're getting the value back in the form request. Request["A.B.C.Name"] and etc.
All the required fields are on the form.
I had deleteOnNull issue with Linq to SQL: How to set DeleteOnNull from designer for future ref if you're using L2SQL.

Resources