Error when creating mappings with AutoMapper - entity-framework

I'm trying to map objects with AutoMapper. I've created the HTTP POST controller method, which should create the new Part object to database. It should add data to both entities, Part and PartAvailabilites. Database is already existing and is scaffolded by EF Core. The error I'm receiving is:
AutoMapper created this type map for you, but your types cannot be mapped using the current configuration Part -> PartDto (Destination member list)PartManagement.Entities.Part -> PartManagement.Dto.PartDto (Unmapped properties:Balance)"
Does anyone know what could be the problem with this mapping? I tried to do the mapping in several ways but none of them is working.
Here is my mapping:
CreateMap<PartDto, PartEntity>()
.ForMember(dest => dest.FkPartAvailability,
opts => opts.MapFrom(src => new PartAvailabilities
{
Balance = src.Balance
}));
Example JSON request:
{
"name": "testPart",
"catalogNumber": 12345,
"balance": 10
}
Here are my entity classes:
public class Part
{
public long Id { get; set; }
public int PartNumber { get; set; }
public string Name { get; set; }
public PartAvailabilities FkPartAvailability { get; set; }
}
public class PartAvailabilities
{
public PartAvailabilities()
{
Parts = new HashSet<Part>();
}
public long Id { get; set; }
public decimal Balance { get; set; }
public ICollection<Part> Parts { get; set; }
}
public class PartDto
{
public string Name { get; set; }
public int PartNumber { get; set; }
public decimal Balance { get; set; }
}
This is Create method in the ManagementService class:
public async Task<PartDto> Create(PartDto request)
{
var part = _mapper.Map<PartDto, PartEntity>(request);
var createdPart = partRepository.Add(part);
await partRepository.UnitOfWork.SaveChangesAsync();
return _mapper.Map<PartDto>(createdPart);
}
And here is HttpPost method from controller:
[HttpPost]
public async Task<IActionResult> Part_Create([FromBody] PartDto request)
{
PartDto createdPart;
try
{
if (request != null)
{
createdPart = await _partManagementService.Create(request);
return Ok(request);
}
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
return Ok(new string[] { "Part created" });
}

The message is trying to tell you that there is no map between Part and PartDto. AM will create a map for you, but that map is not valid, because PartDto.Balance cannot be mapped. So you have to create the map and tell AM how to map Balance. Things might be easier to understand if you set CreateMissingTypeMaps to false.

Related

A complex Entity Framework / AutoMapper REST case

I am assigned the implementation of a REST GET with a complex DB model and somewhat complex output layout. Although I am a REST beginner, I have lost "rest" on this for 2 weeks spinning my wheels, and Google was of no help as well.
Here's a simplification of the existing DB I am given to work with:
Table group : {
Column id Guid
Column name string
Primary key: {id}
}
Table account
{
Column id Guid
Column name string
Primary key: {id}
}
Table groupGroupMembership
{
Column parentGroupId Guid
Column childGroupId Guid
Primary key: {parentGroupId, childGroupId}
}
Table accountGroupMembership
{
Column parentGroupId Guid
Column childAccountId Guid
Primary key: {parentGroupId, childAccountId}
}
So clearly you guessed it: There is a many-to-many relationship between parent a child groups. Hence a group can have many parent and child groups. Similarly, an account can have many parent groups.
The DB model I came up with in C# (in namespace DBAccess.Models.Tables):
public class Group
{
// properties
public Guid id { get; set; }
public string? name { get; set; }
// navigation properties
public List<GroupMemberAccount>? childAccounts { get; set; }
public List<GroupMemberGroup>? childGroups { get; set; }
public List<GroupMemberGroup>? parentGroups { get; set; }
}
public class Account
{
// properties
public Guid id { get; set; }
public string? name { get; set; }
// navigation properties
public List<GroupMemberAccount>? parentGroups { get; set; }
}
public class GroupMemberAccount
{
// properties
public Guid parentGroupId { get; set; }
public Guid childAccountId { get; set; }
//navigation properties
public Group? parentGroup { get; set; }
public Account? childAccount { get; set; }
static internal void OnModelCreating( EntityTypeBuilder<GroupMemberAccount> modelBuilder )
{
modelBuilder.HasKey(gma => new { gma.parentGroupId, gma.childAccountId });
modelBuilder
.HasOne(gma => gma.parentGroup)
.WithMany(g => g.childAccounts)
.HasForeignKey(gma => gma.parentGroupId);
modelBuilder
.HasOne(gma => gma.childAccount)
.WithMany(a => a.parentGroups)
.HasForeignKey(gma => gma.childAccountId);
}
}
public class GroupMemberGroup
{
// properties
public Guid parentGroupId { get; set; }
public Guid childGroupId { get; set; }
//navigation properties
public Group? parentGroup { get; set; }
public Group? childGroup { get; set; }
static internal void OnModelCreating(EntityTypeBuilder<GroupMemberGroup> modelBuilder)
{
modelBuilder.HasKey(gmg => new { gmg.parentGroupId, gmg.childGroupId });
modelBuilder
.HasOne(gmg => gmg.parentGroup)
.WithMany(g => g.childGroups)
.HasForeignKey(gmg => gmg.parentGroupId);
modelBuilder
.HasOne(gmg => gmg.childGroup)
.WithMany(g => g.parentGroups)
.HasForeignKey(gmg => gmg.childGroupId);
}
}
The corresponding DTO model I created:
public class Account
{
public Guid id { get; set; }
public string? name { get; set; }
public List<GroupMemberAccount>? parentGroups { get; set; }
}
public class AccountMappingProfile : AutoMapper.Profile
{
public AccountMappingProfile()
{
CreateMap<DBAccess.Models.Tables.Account, Account>();
}
}
public class Group
{
public Guid id { get; set; }
public string? Name { get; set; }
public GroupChildren children { get; set; } = null!;
};
public class GroupChildren
{
public List<GroupMemberAccount>? childAccounts { get; set; } = null!;
public List<GroupMemberGroup>? childGroups { get; set; } = null!;
}
public class GroupMemberAccount
{
public Guid parentGroupId { get; set; }
public Guid childAccountId { get; set; }
//public Group? parentgroup { get; set; } // commented out because no need to output in a GET request
public Account? childAccount { get; set; }
}
public class GroupMemberGroup
{
public Guid parentGroupid { get; set; }
public Guid childGroupId { get; set; }
//public Group? parentGroup { get; set; }; // commented out because no need to output in a GET request
public Group? childGroup { get; set; };
}
What you need to spot here is the difference in classes Group between the DB and DTO models.
In the DB model, Group has 3 lists: childAccounts, childGroups and parentGroups.
In the DTO model, Group has 1 node children of type GroupChildren which is a class that contains 2 of those 3 lists.
Hence an additional difficulty when it comes to design the mapping. That difference is intentional because it matches the following desired output for an endpoint such as: GET .../api/rest/group({some group guid}) is something like:
{
"id": "some group guid",
"name": "some group name",
"children": {
"childAccounts":{
"account":{ "name": "some account name 1"}
"account":{ "name": "some account name 2"}
...
}
"childFroups":{
"group":{ "name": "some group name 1"}
"group":{ "name": "some group name 2"}
...
}
},
}
obtained from following typical controller code:
[HttpGet("Groups({key})")]
[ApiConventionMethod(typeof(ApiConventions),
nameof(ApiConventions.GetWithKey))]
public async Task<ActionResult<Group>> Get(Guid key, ODataQueryOptions<Group> options)
{
var g = await (await context.Group.Include(g => g.childAccounts)
.Include(g => g.childGroups)
.Where(g => g.id == key)
.GetQueryAsync(mapper, options) // note the mapper here is the mapping defined below
).FirstOrDefaultAsync();
if (g is null)
{
return ResourceNotFound();
}
return Ok(g);
}
So here's the missing part to all this. Unless there are major errors in all of the above, I have a very strong intuition that it is the mapping that is failing to get me the requested output above.
public class GroupMappingProfile : AutoMapper.Profile
{
public GroupMappingProfile()
{
// the rather straightforward.
CreateMap<DBAccess.Models.Tables.GroupMemberAccount, GroupMemberAccount>();
CreateMap<DBAccess.Models.Tables.GroupMemberGroup, GroupMemberGroup>();
//Attempt 1: the not so straightforward. An explicit exhaustive mapping of everything, down to every single primitive type
CreateMap<DBAccess.Models.Tables.Group, Group>()
.ForMember(g => g.children, opts => opts.MapFrom(src => new GroupMembers
{
childAccounts = src.childAccounts!.Select(x => new GroupMemberAccount { parentGroupId = x.parentGroupId,
childAccountId = x.childAccountId,
childAccount = new Account { id = x.childAccount!.id,
name = x.childAccount!.name
}
}
).ToList(),
//childGroups = src.childGroups!.Select(x => new GroupMemberGroup(x)).ToList(),
childGroups = src.childGroups!.Select(x => new GroupMemberGroup { parentGroupId = x.parentGroupId,
childGroupId = x.childGroupId,
childGroup = new Group { id = x.childGroup!.id,
name = x.childGroup!.name
}
}
).ToList(),
}));
//Attempt 2: mapper injection
IMapper mapper = null!;
CreateMap<DBAccess.Models.Tables.Group, Group>()
.BeforeMap((_, _, context) => mapper = (IMapper)context.Items["mapper"]) //ADDING THIS LINE CAUSES ALL QUERIES TO LOOK FOR A NON EXISTENT Group.Groupid column
.ForMember(g => g.children, opts => opts.MapFrom(src => new GroupMembers
{
childAccounts = mapper.Map<List<DBAccess.Models.Tables.GroupMemberAccount>, List<GroupMemberAccount>>(src.childAccounts!),
childGroups = mapper.Map<List<DBAccess.Models.Tables.GroupMemberGroup>, List<GroupMemberGroup>>(src.childGroups!)
}))
}
}
Attempt1 will yield:
{
"id": "some guid",
"name": "some name"
"children": {}
}
even though the generated SQL does fetch all the required data to fill "children"
Attempt2 (mapper injection) is a technique I was suggested and have no clue how it is supposed to work. From what I gather, the mapping functions creates a few maps for some basic types while it uses its "future" self to create the remaining mappings, whenever it will be invoked in the future. Looks somehow like a one-time recursion.
However, it crashes as the generated SQL will look for a non-existent view column group.Groupid
SELECT [t].[id], [t].[name],
[g0].[parentGroupId], [g0].[childAccountId],
[g1].[parentGroupId], [g1].[childGroupId], [g1].[Groupid] -- where does [g1].[Groupid] come from??
FROM (
SELECT TOP(1) [g].[id], [g].[name]
FROM [HID_Rest].[group] AS [g]
WHERE [g].[id] = #__key_0
) AS [t]
LEFT JOIN [HID_Rest].[groupMemberAccount] AS [g0] ON [t].[id] = [g0].[parentGroupId]
LEFT JOIN [HID_Rest].[groupMemberGroup] AS [g1] ON [t].[id] = [g1].[parentGroupId]
ORDER BY ...
So regardless of the mapping profile I experimented with, what is the right mapping profile I need (and what ever else) to get the expected JSON output above? Or is this desired JSON structure possible at all?
After further work, I have figured that there was nothing wrong with my models and mapping. There's still something wrong though as the output to my GET requests is still incomplete. Here's the current new issue I need to deal with to solve this problem:
Issue with REST controller function Microsoft.AspNetCore.Mvc.ControllerBase.OK()?

How to set parent entity Id when posting/creating child entity object with web api

I have two Entity-Framework entities (A child "Note", which belongs to a Folder) and scaffolded web api's for each. I can create Folders and want to be able to add notes to those folders.
public class Note
{
public int Id {get; set; }
[MaxLength(50), Required]
public string NoteTitle { get; set; }
[MaxLength(1000), Required]
public string NoteContent { get; set; }
public NoteFolder NoteFolder { get; set; }
}
public class NoteFolder
{
public int Id { get; set; }
public string FolderName { get; set; }
}
My POST:
let Note = {
"NoteTitle": this.NoteTitle(),
"NoteContent": this.NoteContent(),
"NoteFolder_Id:": ParentFolderId
};
$.ajax({
url: `http://localhost:52657/api/Notes`,
type: 'POST',
dataType: 'json',
contentType: 'application/json',
success: function (data) {
window.location.replace("http://localhost:52657/");
alert(`Successfully Created New Note: ${Note.NoteTitle}`)
},
data: JSON.stringify(Note)
});
Endpoint:
// POST: api/Notes
[ResponseType(typeof(Note))]
public IHttpActionResult PostNote(Note note)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Notes.Add(note);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = note.Id }, note);
}
Whenever I make a post request, the note is created but the foreign key row NoteFolder_Id is null in my database. How do fix this?
Edit: I've tried posting:
"NoteFolder": { "Id": ParentFolderId }
so that it can be serialized properly, however this resulted in a new Folder being created in the db, with an auto incremented Id, and the created Note making reference to that.
Changing Note class and performing migrations,
public class Note
{
public int Id {get; set; }
[MaxLength(50), Required]
public string NoteTitle { get; set; }
[MaxLength(1000), Required]
public string NoteContent { get; set; }
public int? FolderId { get; set; }
[ForeignKey("FolderId")]
public NoteFolder NoteFolder { get; set; }
}
then sending the post in the form:
let Note = {
"NoteTitle": this.NoteTitle(),
"NoteContent": this.NoteContent(),
"FolderId": ParentFolderId
};
fixed the issue.
Still unsure why passing a NoteFolder object resulted in a new folder being created in the db. https://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx this site uses my initial model style to map 1:n.

Web API Get with query parameters on Mongodb collection

I have WebAPI written and it is using mongodb collection as a database.
it looks like
[
{
"Id":"5a449c148b021b5fb4cb1f66",
"airline":[
{
"airlineID":-1,
"airlineName":"Unknown",
"airlineAlias":"",
"airlineIATACode":"-",
"airlineICAOCode":"N/A",
"airlineCallsign":"",
"airlineBaseCountry":"",
"airlineActiveIndicator":"Y"
},
{
"airlineID":1,
"airlineName":"Private flight",
"airlineAlias":"",
"airlineIATACode":"1T",
"airlineICAOCode":"N/A",
"airlineCallsign":"",
"airlineBaseCountry":"",
"airlineActiveIndicator":"Y"
},
{
"airlineID":2,
"airlineName":"135 Airways",
"airlineAlias":"",
"airlineIATACode":"2T",
"airlineICAOCode":"GNL",
"airlineCallsign":"GENERAL",
"airlineBaseCountry":"United States",
"airlineActiveIndicator":"N"
}
]
}
]
I'm trying to get data using airlineIATACode attribute
public airlineModel Get(string i)
{
_collection = _db.GetCollection<airlineModel>("airline");
var res = Query<airlineModel>.EQ(p => p.airline[0].airlineIATACode, i);
return _collection.FindOne(res);
}
My controller implementation
public HttpResponseMessage Get(string IATAcode)
{
var result = objds.Get(IATAcode);
if (result != null)
return Request.CreateResponse(HttpStatusCode.OK, result);
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Data not found");
}
My Model class:
public class airlineModel
{
public ObjectId Id { get; set; }
[BsonElement("airline")]
public List<airlinedata> airline { get; set; }
}
public class airlinedata
{
[BsonElement("airlineID")]
public int airlineID { get; set; }
[BsonElement("airlineName")]
public string airlineName { get; set; }
[BsonElement("airlineAlias")]
public string airlineAlias { get; set; }
[BsonElement("airlineIATACode")]
public string airlineIATACode { get; set; }
[BsonElement("airlineICAOCode")]
public string airlineICAOCode { get; set; }
[BsonElement("airlineCallsign")]
public string airlineCallsign { get; set; }
[BsonElement("airlineBaseCountry")]
public string airlineBaseCountry { get; set; }
[BsonElement("airlineActiveIndicator")]
public string airlineActiveIndicator { get; set; }
}
When I run app and browse http://localhost:60387/api/airlineAPI?IATAcode=1T
it says, Data not found
What can I do to solve this problem?
You can try this.
public airlinedata Get(string i)
{
var _collection = database.GetCollection<airlineModel>("airline");
var filter = Builders<airlineModel>.Filter
.ElemMatch(model => model.airline, airline => airline.airlineIATACode == i);
var projection = Builders<airlineModel>.Projection
.Include(model => model.airline[-1]);
var airlineModel = _collection.Find(filter)
.Project<airlineModel>(projection)
.Single();
return airlineModel.airline.Single();
}
Note that you don't need to put mapping attributes on each field. The default mapping does exactly what you did with attributes.
The only attribute I suggest you to use is [BsonId] to tell MongoDB which is your _id field.
public class airlineModel
{
[BsonId(IdGenerator = typeof(ObjectIdGenerator))]
public ObjectId Id { get; set; }
...
}
Finally, in case of a large collection, don't forget to create an index on airlineIATACode field, otherwise the search would perform an expensive COLLSCAN.

Entity framework replaces delete+insert with an update. How to turn it off

I want to remove a row in database and insert it again with the same Id, It sounds ridiculous, but here is the scenario:
The domain classes are as follows:
public class SomeClass
{
public int SomeClassId { get; set; }
public string Name { get; set; }
public virtual Behavior Behavior { get; set; }
}
public abstract class Behavior
{
public int BehaviorId { get; set; }
}
public class BehaviorA : Behavior
{
public string BehaviorASpecific { get; set; }
}
public class BehaviorB : Behavior
{
public string BehaviorBSpecific { get; set; }
}
The entity context is
public class TestContext : DbContext
{
public DbSet<SomeClass> SomeClasses { get; set; }
public DbSet<Behavior> Behaviors { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<SomeClass>()
.HasOptional(s => s.Behavior)
.WithRequired()
.WillCascadeOnDelete(true);
}
}
Now this code can be executed to demonstrate the point
(described with comments in the code below)
using(TestContext db = new TestContext())
{
var someClass = new SomeClass() { Name = "A" };
someClass.Behavior = new BehaviorA() { BehaviorASpecific = "Behavior A" };
db.SomeClasses.Add(someClass);
// Here I have two classes with the state of added which make sense
var modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// They save with no problem
db.SaveChanges();
// Now I want to change the behavior and it causes entity to try to remove the behavior and add it again
someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" };
// Here it can be seen that we have a behavior A with the state of deleted and
// behavior B with the state of added
modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// But in reality when entity sends the query to the database it replaces the
// remove and insert with an update query (this can be seen in the SQL Profiler)
// which causes the discrimenator to remain the same where it should change.
db.SaveChanges();
}
How to change this entity behavior so that delete and insert happens instead of the update?
A possible solution is to make the changes in 2 different steps: before someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" }; insert
someClass.Behaviour = null;
db.SaveChanges();
The behaviour is related to the database model. BehaviourA and B in EF are related to the same EntityRecordInfo and has the same EntitySet (Behaviors).
You have the same behaviour also if you create 2 different DbSets on the context because the DB model remains the same.
EDIT
Another way to achieve a similar result of 1-1 relationship is using ComplexType. They works also with inheritance.
Here an example
public class TestContext : DbContext
{
public TestContext(DbConnection connection) : base(connection, true) { }
public DbSet<Friend> Friends { get; set; }
public DbSet<LessThanFriend> LessThanFriends { get; set; }
}
public class Friend
{
public Friend()
{Address = new FullAddress();}
public int Id { get; set; }
public string Name { get; set; }
public FullAddress Address { get; set; }
}
public class LessThanFriend
{
public LessThanFriend()
{Address = new CityAddress();}
public int Id { get; set; }
public string Name { get; set; }
public CityAddress Address { get; set; }
}
[ComplexType]
public class CityAddress
{
public string Cap { get; set; }
public string City { get; set; }
}
[ComplexType]
public class FullAddress : CityAddress
{
public string Street { get; set; }
}

Mapping ICollection to IEnumerable using automap issue

Stuck on a strange issue, that works using my get method but fails when returning iqueryable. Some limitation when using projection or iqueryable?
My code looks like below, simplified:
public class SimpleEntity
{
public long Id { get; set; }
public string Name { get; set; }
public virtual SimpleEntity Parent { get; set; }
public virtual ICollection<SimpleEntity> Children { get; set; }
public SimpleEntity()
{
Children = new List<SimpleEntity>();
}
}
public class SimpleEntityResponseDTO
{
public long Id { get; set; }
public string Name { get; set; }
public NameValueItem ParentReferral { get; set; }
public IEnumerable<NameValueItem> ChildReferrals { get; set; }
public NavigationFolderResponseDTO()
{
ChildReferrals = new List<NameValueItem>();
}
}
public class NameValueItem
{
public long Value { get; set; }
public string Name { get; set; }
}
The web api actions:
[HttpGet, Queryable]
public IQueryable<SimpleEntityResponseDTO> List()
{
//Generic crudservice returning an iqueryable based on Set<SimpleEntity>
return _crudService.QueryableList().Project().To<SimpleEntityResponseDTO>();
}
[HttpGet]
public HttpResponseMessage Get(long id)
{
SimpleEntity result = _crudSrv.Get(id);
if (result != null)
return Request.CreateResponse<SimpleEntityResponseDTO>(HttpStatusCode.OK, Mapper.Map<SimpleEntity , SimpleEntityResponseDTO>(result));
else
return Request.CreateResponse(HttpStatusCode.NotFound);
}
And now the mapping:
Mapper.CreateMap<SimpleEntity, SimpleEntityResponseDTO>()
.ForMember(to => to.ParentReferral, opt => opt.MapFrom(from => new NameValueItem { Name = from.Parent.Name, Value = from.Parent.Id }))
.ForMember(to => to.ChildReferrals, opt => opt.MapFrom(from => from.Children.Select(o => new NameValueItem {Name = o.Name, Value = o.Id}).ToList() ));
The parent mapping works no matter what. But the Children mapping is causing below issue.
When retrieving an object through the get method everything works, no matter wich entity i retrieve. When using List i get "Object reference not set to an instance of an object", "d__b.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n". Tried for example adding $filter=Id eq 5 (or whatever id) but results in same issue. Perhaps someone can hint me to what goes wrong here?