Comments (13)
When you say non-identifying do you mean it doesn't have any Key properties at all? If it doesn't then there's no way for AM.Collections to figure out what is equal to what and defaults to AutoMapper's collection mapping.
If this is not the case can you provide a gist of an example of it failing? Or at least show your Entities and Dtos. Hopefully you aren't creating the mapper before every persist call.
from automapper.collection.
When I say "Non-identifying" I'm referring to the primary key of the child object not containing a foreign key to the parent object. It does have a primary key of its own. It also has a foreign key to the parent, but that property isn't part of its primary key.
It's the children with the identifying relationship (where the primary key of the child includes the identifier of the parent) that has the problem.
I will put together a simplified example and post it. Thanks!
from automapper.collection.
My best guess is that you don't have the complete Key set in property map for the Identifying relationship.
ID is mapped but ParentID isn't, because you can get away with it in non-identifying relationships. The thing with AM.Collections.EF is that if the complete key doesn't have property maps it will say it can't generate an equivalency and default back to AM collection functionality. You either have to map the ParentID or explicitly make an equality expression for that map using just ID and not ParentID.
If this isn't the case a complete example would really help.
from automapper.collection.
When you say "map the parent ID" you mean that my mapping in the AutoMapper profile has to explicitly handle (with a ForMember) the parent ID property?
Do you mean that I need to explicitly specify the properties to be used as the identifier? (I've forgotten the name of the AM.Collection class that you do this with.) But I thought AM.Collections.EF could do this automatically, based on the EF config.
Or do you mean that my EF config doesn't include the full key of the object? It does, as the gist will show.
My gist is here: https://gist.github.com/alewkowicz/a75add41d1d2165f8a100539a4700d98
from automapper.collection.
This is REALLY confusing, but I think what the issue is that what you expect to happen is incorrect.
From what I've seen in AM.Collection and EF6 in general with lists, is that Identity and Non-Identity have the exact same functionality when updating with no changes. If you don't change anything, which from your example seems like you are not, then every item should be unchanged, because well it didn't change anything.
From your Original post it seemed like the application was adding and removing items from the Identity Collection, which SHOULD NOT happen if the objects are all the same.
The only difference I've found with Identity and Non-Identity relationships is that when you actually remove an object from a collection the Identity relationship will Delete it, where as the Non-Identity will Orphan it. The Non-Identity will set the FK property to null. In your example LogId will be set to null when removed from the Log.Events list. If LogId is int and not int? then it will throw and exception saying it can't set to null.
https://stackoverflow.com/questions/11033348/is-it-possible-to-remove-child-from-collection-and-resolve-issues-on-savechanges explains the situation people usually have. And it's mostly from people having a Non-Identity relationship and expecting removing from the list to mark it as deleted.
from automapper.collection.
I understand the behavior described in the links. What I saw was the application adding and removing items from the child collection, which I agree should not happen given how I had things configured.
But I think I've figured it out, thanks to your remark about maybe not having the complete Key set in the property map.
The raw data I get back from the API and convert into POCOs doesn't have parent id fields. The EF POCOs I map to, do. They need them for the database.
I was handling this by adding them manually in an AfterMap
call on the parent object:
.AfterMap((apiLog, dbLog) =>
{
foreach (var ev in dbLog.Events)
{
ev.Log = dbLog;
ev.LogId = dbLog.Id;
}
}
But I suspect that's too late in the process: the AM.Collection.EF
comparison of the child object to the DB is probably happening before that parent object AfterMap
.
So I added a ParentID
to the DTO POCOs. The process that maps the API data to the POCOs will default it to 0. And then I created a BeforeMap
call on the parent object, to populate the ParentID
on the source object:
profile.CreateMap<ApiLog.Log, Db.Log>()
.BeforeMap((api, db) =>
{
if (api.Events != null)
{
foreach (var e in api.Events)
{
e.Event.LogId = api.Id;
}
}
})
Once I did this, that test method I put in the gist started behaving differently. It left all the child objects unchanged. No longer adding and deleting.
So, my conclusion: if I used InsertOrUpdate
, it appeared that the child objects were compared to the database before the parent object AfterMap
that finished populating them, ran.
Does this make sense?
I don't want to have to pre-process the objects I've materialized from the API data before I feed them to AutoMapper. I don't want the API POCOs to have to have fields that don't come from the API data. But that's the workaround I'm facing.
Thanks.
from automapper.collection.
Actually, my memory from when I looked at the InsertOrUpdate<T>
code was that you did the EF entity-finding BEFORE you map the child object. So it didn't matter when in the mapping I populated the target object's ParentID
: you'd use the source object's values to see whether there was a matching object already.
Is there a way to configure things so that I can avoid forcing a ParentID
property into my source object? That I can supply that value "on the fly" from the ResolutionContext
, or something?
Thanks for all your help. I appreciate this.
from automapper.collection.
You should just be able to use AM.Collection's EqualityComparison which will overwrite what AM.Collections.EF does with the key properties.
CreateMap<EntityDto,Entity>().EqualityComparison((edto, e) => edto.ID == e.ID);
Then you don't have to pass LogID for the EntityDto from the API just so it won't add/remove. Setting LogID isn't important to set anyways, because having it in the child list EF will handle that.
If you have a convention that creates the Maps automatically and don't want to call CreateMap explicitly then you can look at GenerateEntityFrameworkPrimaryKeyPropertyMaps
code and write your own to use all primary key values that have matches, instead of defaulting to AM logic when not all match.
Example of what it might look like.
public class GenerateEntityFrameworkPartialPrimaryKeyPropertyMaps<TDatabaseContext> : IGeneratePropertyMaps
where TDatabaseContext : IObjectContextAdapter, new()
{
private readonly TDatabaseContext _context = new TDatabaseContext();
private readonly MethodInfo _createObjectSetMethodInfo = typeof(ObjectContext).GetMethod("CreateObjectSet", Type.EmptyTypes);
public IEnumerable<PropertyMap> GeneratePropertyMaps(TypeMap typeMap)
{
var propertyMaps = typeMap.GetPropertyMaps();
try
{
var createObjectSetMethod = _createObjectSetMethodInfo.MakeGenericMethod(typeMap.DestinationType);
dynamic objectSet = createObjectSetMethod.Invoke(_context.ObjectContext, null);
IEnumerable<EdmMember> keyMembers = objectSet.EntitySet.ElementType.KeyMembers;
var primaryKeyPropertyMatches = keyMembers.Select(m => propertyMaps.FirstOrDefault(p => p.DestinationProperty.Name == m.Name)).Where(pm => pm != null); // Added Where statement to ignore primary key values that don't have matches for partial matches
return primaryKeyPropertyMatches;
}
catch (Exception)
{
return Enumerable.Empty<PropertyMap>();
}
}
}
from automapper.collection.
Thank you! I will investigate, first the Id
only option, then the custom object, if necessary.
from automapper.collection.
The ID only option works without throwing errors, but I have a question.
In the case I'm dealing with, the Event
entity's primary key includes the parent ID because the provided Id
value from the API data is not unique across the entire domain. It's only unique within the children of a given Log
.
You suggest
CreateMap<EntityDto,Entity>().EqualityComparison((edto, e) => edto.ID == e.ID);
... but I'm concerned I might get a hit on an Event
from a different Log
.
If I did, I believe the mapping of the LogId
in the AfterMap
for the Log
entity would wind up overwriting the LogId
in the child, which would result in an EF exception.
That hasn't happened yet in my testing, though. So, my question:
Are AM
and AM.Collection.EF
smart enough that they'll only be searching the collection of known children of the parent entity when they try to match a child using the EqualityComparison
, or do they search the entire DbSet
?
Thanks!
from automapper.collection.
They compare just the collection, because that's the only thing they have to compare with AM.Collection
mappers. They have no access or Idea of DBSet, it's just AM.Collection.EF
uses the schema to generate the equality comparison that's given to AM.Collection
which has no reference to EF.
You really shouldn't have that scenario happen anyways because of how the DB should be set up. I would more than likely duplicate the Event
under the Log
with the same child ID, or if there's a match by ID would copy to that. Also you shouldn't have to set 'LogID' in After Map because just being part of the child collection will handle that for you. Unless there's something else in the code that requires it to work that I'm unaware of.
It's not that they are smart enough to just pick the child collection. It's actually they are stupid enough not to know that there's anything else. :)
from automapper.collection.
Thank you!
from automapper.collection.
I will close this now as the issue is resolved. Thank you again!
from automapper.collection.
Related Issues (20)
- How to use Collections with Profile configuration? HOT 5
- When mapping Lists, if the DestinationType is added to a DataContext, automapper fails with an exception HOT 1
- How to use with a .Net 5 Web project and DI HOT 1
- EqualityComparison based on the index of item in collection
- Add support for AutoMapper 11.0.0 HOT 1
- Package version constraint violation with AutoMapper.Collection HOT 1
- Are nested lists supported?
- mapping foreign key
- EquivalentExpressionAddRemoveCollectionMapper should implement IObjectMapperInfo HOT 1
- Could not install on .NETFramework,Version=v4.8 HOT 2
- Support for AutoMapper v12 HOT 2
- Insert Master with details HOT 1
- DI cycles forever when calling AddCollectionMappers() twice HOT 3
- Collections with Automapper 12.0.1 doesn't work HOT 1
- Expression of type '<Type>' cannot be used for parameter of type '<OtherType>' of method 'Void Add(<OtherType>)' (Parameter 'arg0') HOT 3
- Attribute based EqualityComparison HOT 1
- Order of lists is not preserved during updates HOT 1
- Dependency Contraint HOT 1
- AutoMapper 13 support
- Automapper and ObservableCollection HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from automapper.collection.