Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to map a child item when creating a parent item? (not a bug more a question) #41

Closed
MaRRiK74 opened this issue Nov 13, 2021 · 5 comments
Assignees
Labels

Comments

@MaRRiK74
Copy link

MaRRiK74 commented Nov 13, 2021

Is it even possible like this?

TestDbContext db = await TestDbContext.CreateAsync();

Role role = new Role() { Name = "Test" };
db.Roles.Add(role);
await db.SaveChangesAsync();

// Create user and assign existing role
User user = new() { Name = "Example" };
user.Roles.Add(role);

await db.MapAsync<User>(user);

await db.SaveChangesAsync();
@leonardoporro leonardoporro self-assigned this Nov 13, 2021
@leonardoporro leonardoporro added this to To do in Detached Mapper via automation Nov 13, 2021
@leonardoporro
Copy link
Owner

leonardoporro commented Nov 13, 2021

Yes it is. I double checked just in case, this is the working code:

            TestDbContext db = await TestDbContext.CreateAsync();

            Role role = new Role() { Name = "Test" };
            db.Roles.Add(role);
            await db.SaveChangesAsync();

            // Create user and assign existing role
            User user = new() { Name = "Example" };
            user.Roles = new List<Role>(); // null reference here! in the old code.
            user.Roles.Add(role);

            await db.MapAsync<User>(user);

            await db.SaveChangesAsync();
   
           // result will contain an user with the assigned role
            var result = db.Users.Where(u => u.Name == "Example").FirstOrDefault();
       

@MaRRiK74
Copy link
Author

Thanks for the quick response. The null ref was not the issue on my side (my model always creates a new list).
I had missed something elsewhere. So this part does work, I wanted to close/delete this issue but you already responded :-)

But now I am facing the issue that after saving the dbcontext, the PK of the parent Item is always '0' in this case the user.Id is always 0. Is there an option to tell detached-mapper to return the value which was assigned?

@leonardoporro
Copy link
Owner

leonardoporro commented Nov 13, 2021

So, the mapper works with two concepts, the DTO and the Entity which may be the same type, or not.

For example:

  • User to User map / dto: User and entity: User.
  • UserDTO to User map / dto: UserDTO and entity User.

Mapper then will:

  • Load root Entity along with [Compositon] children recursively and only the Id of [Aggregation] children (the graph).
  • Start traversing the graph and copying the properties.
    For [Aggregation]
  • Copy only the Id
  • Mark the Entity as Unchanged.
  • Do not recurse! Aggregations end here.
    For [Composition]
  • If DTO exists and Entity doesn't, mark as Added
  • If DTO doesn't exist and Entity does, mark as Deleted
  • If both exists, mark as Modified.
  • Continue recursively with the rest of the Complex or Collection properties.

[Aggregation] are supossed to be independent entities/graphs that the entity is referring to. And shouldn't be added/deleted along with it. e.g.: Invoice -> InvoiceType
That's why only Id is mapped for aggregations and is marked as Unmodified. If the aggregated entity doesn't exists, an fk error is thrown when saving.

[Composition] are entities that form part of the same concept and should be updated/deleted with the root entity.
e.g.: Invoice -> InvoiceDetail.

Another interesting thing. Given the previous DTO and Entity definitions, values are always copied, even when they are the same type. The User that you passed to the MapAsync method is not the same that gets attached to the context.
The attached one comes from the initial query or is instantiated by the mapper, and it's returned by the MapAsync method.
var attachedUser = await db.MapAsync<User>(detachedUser);
In this example, attached and detached users are not the same instance.

In this your case, MapAsync will:

  • Load the root, but the key is empty so it will create a new User.
  • Copy Id, Name, DoB and other simple values
  • Recursively iterate Roles
  • There will be 1 role DTO and 0 role Entities, so it will create a Role and since it is an [Aggregation] it will copy the Id and mark it as Unmodified.

This is a very high level description, as there much more, like circle dependencies (BackReference in the code), type configuration, mapping configuration, the dynamic code, etc.

Feel free to ask on a certain topic if you are interested on it.

@leonardoporro
Copy link
Owner

Thanks for the quick response. The null ref was not the issue on my side (my model always creates a new list). I had missed something elsewhere. So this part does work, I wanted to close/delete this issue but you already responded :-)

But now I am facing the issue that after saving the dbcontext, the PK of the parent Item is always '0' in this case the user.Id is always 0. Is there an option to tell detached-mapper to return the value which was assigned?

Sorry, we were writting posts at the same time, but I anwsered your question over there.
The instance that you pass to MapAsync is your "DTO", please take a look at the MapAsync return value and let me know if you can't find your Id there.

@MaRRiK74
Copy link
Author

Ah the return value, thanks for pointing that out! With that I can work something out. Many thanks again.

Detached Mapper automation moved this from To do to Done Nov 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Development

No branches or pull requests

2 participants