automapper / automapper.collection Goto Github PK
View Code? Open in Web Editor NEWAutoMapper support for updating existing collections by equivalency
License: MIT License
AutoMapper support for updating existing collections by equivalency
License: MIT License
happens when I run
cfg.AddProfile<CollectionProfile>();
small demo to reproduce issue
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AutoMapper" version="5.0.0" targetFramework="net452" />
<package id="AutoMapper.Collection" version="2.0.0" targetFramework="net452" />
</packages>
namespace ConsoleApplication9
{
public class Invoice
{
public int InvoiceId { get; set; }
public ICollection<LineItem> LineItems { get; set; }
}
public class LineItem
{
public int LineItemId { get; set; }
}
public class InvoiceModel
{
public int InvoiceId { get; set; }
public IEnumerable<LineItemModel> LineItems { get; set; }
}
public class LineItemModel
{
public int LineItemId { get; set; }
}
class TestProfile : Profile
{
public TestProfile()
{
CreateMap<InvoiceModel, Invoice>()
.ForMember(dest => dest.LineItems, opt => opt.UseDestinationValue())
;
CreateMap<LineItemModel, LineItem>()
.EqualityComparision((model, entity) => model.LineItemId == entity.LineItemId);
}
}
class Program
{
static void Main(string[] args)
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<CollectionProfile>();
cfg.AddProfile<TestProfile>();
});
var invoice = new Invoice()
{
InvoiceId = 1,
LineItems = new List<LineItem>()
{
new LineItem() { LineItemId = 1 }
}
};
var model = new InvoiceModel()
{
InvoiceId = 1,
LineItems = new List<LineItemModel>()
{
new LineItemModel() { LineItemId = 1 }
}
};
Mapper.Map(model, invoice);
Console.ReadLine();
}
}
}
I recently had a problem that took ages to solve.
Refer to this issue for details:
AutoMapper/AutoMapper#2612
Basically:
https://github.com/AutoMapper/AutoMapper.Collection/blob/master/src/AutoMapper.Collection.EntityFramework/GenerateEntityFrameworkPrimaryKeyPropertyMatches.cs#L24
_context.ObjectContext
throws an exception if DbContext is invalid.
In my case the default context constructor used a connection string which was invalid on all but my development machine.
This makes the function return empty property maps, so it looks like mapping fails because of missing maps.
It would be great to throw an exception stating that the context is invalid instead. Would have saved me days of bug hunting :)
I really really like your extension for Automapper. But I just noticed a few word-spelling errors:
Equivilency -> Equivalency
Equivilent, Equivlent -> Equivalent
Comparision -> Comparison
Persistance -> Persistence
GeneratEquivilentExpression -> GenerateEquivalentExpression
Is it possible to fix them?
When using "normal" Automapper, and using Map(from, to, options)
the options are passed down when mapping related child objects.
When using Automapper.Collection the options object is not available in child mappings.
This Code (LINQPad) demonstrates the issue.
Uncomment the 3 lines to highlight the problem.
void Main()
{
var automapperConfig = new MapperConfiguration(cfg =>
{
cfg.CreateMap<OrderDto, Order>()
// .EqualityComparision((d, e) => d.Id == e.Id)
.AfterMap((d,e,ctx) => Console.WriteLine($"Mapping Order. Expecting 1 entry in Options.Items. Options.Items has {ctx.Options.Items.Count} entries."));
cfg.CreateMap<DetailDto, Detail>()
// .EqualityComparision((d, e) => d.Id == e.Id)
.AfterMap((d,e,ctx) => Console.WriteLine($"Mapping Detail. Expecting 1 entry in Options.Items. Options.Items has {ctx.Options.Items.Count} entries."));
// cfg.AddProfile<CollectionProfile>();
});
IMapper mapper = new Mapper(automapperConfig);
var dto = new OrderDto { Id = 1, Name = "NewOrderName", Details = new HashSet<DetailDto> { new DetailDto { Id = 1, Name = "NewDetailName" } } };
var entity = new Order { Id = 1, Name = "OrderName", Details = new HashSet<Detail> { new Detail { Id = 1, OrderId = 1, Name = "DetailName" } } };
Console.WriteLine("Before mapping:");
entity.Dump();
mapper.Map(dto, entity, opts => opts.Items.Add("1", 1));
Console.WriteLine("After mapping:");
entity.Dump();
}
// Define other methods and classes here
public class Order
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Detail> Details { get; set; }
public Order()
{
Details = new HashSet<Detail>();
}
}
public class Detail
{
public int Id { get; set; }
public string Name { get; set; }
public Order Order { get; set; }
public int OrderId { get; set; }
}
public class OrderDto
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<DetailDto> Details { get; set; }
public OrderDto()
{
Details = new HashSet<DetailDto>();
}
}
public class DetailDto
{
public int Id { get; set; }
public string Name { get; set; }
}
When I using the the collection mapper with entities loaded from EF7 the equivlent expression mapper return that it not shoul be used for the type. The IsMatch method should only verify the type of the data, not if the data contains any values.
I was not able to get the same issue when I not was using EF7 and putting together a simple unit-test was not succeeded either.
I ran into a situation involving collections and Automapper which I solved through a less-than-intuitive workaround. I've tried to describe the problem, and my solution, in the first answer to https://stackoverflow.com/questions/50382252/mapping-collections-in-automapper/50421075.
If my speculations on how Automapper handles collections are correct, would it be possible to tweak the logic so it ignores differences in collection types between destination and source, as long as the destination collection type can accept an "add"?
I'd also be interested in correcting my theory, if something else is going on. Thanx!
When an item is removed from the list, it would be nice to have a callback that can allow me to deal with it.
In that call back, I could tell entity framework that the entity is "deleted". This could even be the default action so that I can really synchronize my DTO with the entity.
But another option would be to allow me to return false, and not actually remove it from the list. (I would probably be setting an IsActive = false in that case.
Really, I just want the first scenario for my current code. (So I don't have to handle delete separately.) But I can see the "IsActive" use case being useful.
Either way, this is a great project! It has really helped me out.
During startup we run a routine that looks through the assemblies to find what it should map. Everything has run fine for the last 6 months. On upgrade to 6.2.2, I added the new way of initializing am.collection, but it blows up with the "Value attempted...." error.
I have looked through all the issues that seem to be related to this but nothing stands out as a cure for my particular case. Here is my code.
public static class AutomapperConfigurator
{
public static void LoadMapsFromCallerAndReferencedAssemblies(Func<AssemblyName, bool> assemblyFilter = null)
{
var target = Assembly.GetCallingAssembly();
Func<AssemblyName, bool> loadAllFilter = (x => true);
var assembliesToLoad = target.GetReferencedAssemblies()
.Where(assemblyFilter ?? loadAllFilter)
.Select(a => Assembly.Load(a))
.ToList();
assembliesToLoad.Add(target);
LoadMapsFromAssemblies(assembliesToLoad.ToArray());
}
public static void LoadMapsFromAssemblies(params Assembly[] assemblies)
{
var types = assemblies.SelectMany(a => a.GetExportedTypes()).ToArray();
Mapper.Initialize(cfg =>
{
// Add this line to initialize am.collections v3 with AM 6.2.2
cfg.AddCollectionMappers();
Load(cfg, types);
});
}
private static void Load(IMapperConfigurationExpression cfg, Type[] types)
{
LoadIMapFromMappings(cfg, types);
LoadIMapToMappings(cfg, types);
LoadCustomMappings(cfg, types);
// Commented out for the update to newer Automapper package. DMC
// cfg.AddProfile<CollectionProfile>();
}
Other info: This is .Net 4.6.1. AM.Collection is v3.1.3. The above code is called during Startup of the WebApi hosted in Owin. The exception occurs after stepping through the cfg.AddCollectionMappers() line, but it takes a few seconds to appear (blocking in the meantime).
Thanks in advance.
We upgraded from AutoMapper 4.x to AutoMapper 6.2.1 and therefore we also upgraded AutoMapper.Collection to 3.1.3 (coming from 2.1.1).
Now our mappings no longer work with Collections<TBase> when the collection contains derived types of TBase. Because it’s quite complicated to describe I created a test solution with two projects, one project contains a a fix/workaround in AutoMapper.Collection (downloaded the source from Github and marked the parts which I've changed with //note change to make it work and one project with the NuGet package from nuget.org.
Note: Depending on where (mappings for the base or concrete classes[.CreateMap<>]) you put your .EqualityComparison(....) it behaves differently, either you get an exception --> that happens when you put a .EqualityComparison(..) to your base mappings and your derived mappings or at least if you put it only on the derived mappings.
And if you put the .EqualityComparison() only in your base mapping it isn't used at all.
)
It would be great if you can have a look and hopefully fix the issue.
Br
Renè
AutoMapper.CollectionIssue.zip
Hello, just had a look at your project, and looks really promising for my needs.
Is it possible to define the collection item matching based on the existence of the KeyAttribute
on target and source types? It would realy make things much easier, and would give a cross-ORM, nice way of controlling the logic.
Either support arrays or change README.md
to List<T>
accordingly.
AutoMapper.Collection works with arrays as indicated in README.md
AutoMapper.Collection works only with List<T>
Modify test cases to use arrays instead of lists, see MapCollectionWithEqualityTests.cs
in AutoMapper.Collection.Tests
project.
If using DbSet.Persist().InserOrUpdate() how am i supposed to save child collections if i have disabled EF lazy loading?
I cant really use Include() as Persist only works on DbSet.
It seems that i cannot save entity child collections through Persist without enabling lazy loading?
public class GroupPerUser
{
public long UserProfileId { get; set; }
public UserProfile UserProfile { get; set; }
public long GroupId { get; set; }
public VehicleGroup Group { get; set; }
}
public class UserProfile : IdentityUser<long>, IDomainEntity
{
public virtual List<GroupPerUser> EnabledGroups { get; set; }
}
public class UserProfileVMTest : UserProfile
{
}
services.AddAutoMapper(c => { c.AddCollectionMappers(); c.AddProfile<MyMapperProfile>(); });
[...]
CreateMap<GroupPerUser, GroupPerUser>()
.EqualityComparison((g1, g2) => g1.UserProfileId == g2.UserProfileId && g1.GroupId == g2.GroupId);
From my understanding using AddCollectionMappers collections have to be updated depending on equality comparision.
In this case i see that collection is first cleared then filled. This produce an issue with EF changeTracker.
protected IMapper Mapper
{
get
{
if (_mapper == null)
_mapper = _serviceProvider.GetService<IMapper>();
return _mapper;
}
}
[...]
var debugPlan = Mapper.ConfigurationProvider.BuildExecutionPlan(typeof(UserProfileVMTest), typeof(UserProfile));
Mapper.Map<UserProfileVMTest, UserProfile>(Model, Entity);
I am not sure if this is an issue or not.
I have an object that has list1 = List and list2 = list.
When I move a bar from 1 list to another, the mapping part works fine but when I try to save the changes (using EF) it throws and error ... "cannot be tracked because another instance with the same key value for {'Id'} is already being tracked"
I assume Collection is treating each list independently so it is trying to delete bar to remove it from list1 and then it creates a new bar to add it to list2, which is causing a same key error. Is that assumption correct and is there any way around it?
I have started using this extension, and just want to say its excellent, thank you!
Now i have an issue, where an object can be moved from 1 collection, into another collection, and when i do this, i get an exception
InvalidOperationException: Multiplicity constraint violated
I am guessing this is because the object isnt being found in the original collection, and this extension is adding the object to the new collection, even though i want it too be moved, then upon saving, EF throws the exception, because i have 2 objects with the same key against my context.
But how can i get this to work?
So if i have the following object structure
MyRoot
| Collection
| MyChild
| Collection
| MyObject (1)
| MyChild
| Collection
| MyObject (2)
How can i move MyObject (1)
into the same collection as MyObject (2)
??
These are all basic objects, and here is some simple code
public class MyRoot
{
public int Id { get; set; }
public ICollection<MyChild> MyChildren { get; set; }
}
public class MyChild
{
public int Id { get; set; }
public int RootId { get; set; }
public MyRoot Root { get; set; }
public ICollection<MyObject> MyObjects { get; set; }
}
public class MyObject
{
public int Id { get; set; }
public string Name { get; set; }
public int ChildId { get; set; }
public MyChild Child { get; set; }
}
Each of these objects have a DTO, for the sake of this example, lets just say the objects are exactly the same, with extension DTO on the end (this is not the case in real application)
In my application, i then have an automapper profile, like so
internal class MyProfile: Profile
{
public MyProfile()
{
this.CreateMap<MyRoot, MyRootDTO>()
.ReverseMap();
this.CreateMap<MyChild, MyChildDTO>()
.ReverseMap()
.EqualityComparison((s, d) => s.Id == d.Id);
this.CreateMap<MyObject, MyObjectDTO>()
.ReverseMap()
.EqualityComparison((s, d) => s.Id == d.Id);
}
}
On my web api controller method, i have this, which is very simple
public async Task<IActionResult> UpdateAsync([FromBody] MyRootDTO model)
{
// get the object and all children, using EF6
var entity = await _service.GetAsync(model.Id);
// map
_mapper.Map(model, entity);
// pass object now updated with DTO changes to save
await _service.UpdateAsync(entity);
// return
return new OkObjectResult(_mapper.Map<MyRootDTO>(entity));
}
If someone could please help, that would be great!
Dear TylerCarlson,
Could you take time to look below simple example?
I want update exist blog with late-added post, since the exist blog already has 2 posts, and new blog has 1 post, so I expected the updated exist blog should has 3 posts, but it did not happen.
thank you very much!
using AutoMapper;
using AutoMapper.EquivilencyExpression;
using AutoMapper.Mappers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UnitTest
{
public class Blog
{
public Blog()
{
LstPost = new List<Post>();
}
public int BlogId { get; set; }
public string BlogUrl { get; set; }
public List<Post> LstPost { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string PostTitle { get; set; }
public string Content { get; set; }
}
[TestClass]
public class AutoMapCollection
{
private IMapper CreateMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<CollectionProfile>();
cfg.CreateMap<Post, Post>().EqualityComparision((n, o) => n.PostId == o.PostId);
cfg.CreateMap<Blog, Blog>();
});
return config.CreateMapper();
}
[TestMethod]
public void AddPostToBlog()
{
var existBlog = new Blog();
existBlog.BlogId = 1;
existBlog.BlogUrl = "blog url";
var post1 = new Post() { PostId = 1, PostTitle = "post1" };
var post2 = new Post() { PostId = 2, PostTitle = "post2" };
existBlog.LstPost.Add(post1);
existBlog.LstPost.Add(post2);
var newBlog = new Blog();
newBlog.BlogId = 1;
newBlog.BlogUrl = "updated url";
var post3 = new Post() { PostId = 3, PostTitle = "post3" };
newBlog.LstPost.Add(post3);
var mapper = CreateMapper();
mapper.Map<Blog, Blog>(newBlog, existBlog);
Assert.AreEqual(3, existBlog.LstPost.Count);
}
}
}
On AutoMapper.Collection 3.1.3
During my test code I initialize multiple AutoMapper instances. Since my tests run in parellel, the creation of a new Mapper instance should be thread-safe, which it is. (Probably since the static API was deprecated).
Now, since I added AutoMapper.Collection my tests fail with the following exception:
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
at AutoMapper.EquivalencyExpression.EquivalentExpressions.<>c__DisplayClass5_0`1.<InsertBefore>b__1(IConfigurationProvider c)
at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression configurationExpression)
at MyApp.CreateAutoMapper()
After a bit of digging it turns out that EquivalentExpressions.AddCollectionMappers()
is not thread safe, because the static dictionary EquivalentExpressionDictionary
is not.
The issue is easily reproducable with the following test:
[Fact]
public async Task ConfigShouldBeThreadSafe()
{
Action act = () =>
{
new AutoMapper.MapperConfiguration(cfg =>
{
cfg.AddCollectionMappers();
});
};
var tasks = new List<Task>();
for (var i = 0; i < 5; i++)
{
tasks.Add(Task.Run(act));
}
await Task.WhenAll(tasks.ToArray());
}
https://gist.github.com/TylerCarlson1/c12da95bf9cdd935b01b
Generic rudimentary implementation for finding orphans in an DataBaseContext.
Override Save changes with
public override int SaveChanges()
{
new DeleteOrphans<AppsMergerContext>(this).DeleteOrphanedEntries();
return base.SaveChanges();
}
Try to integrate this in with the Persist system, so user does have to handle this scenario.
I'm facing a problem when i try to attach an existing entity to my object. I can update existing children properties, remove associations, and add new children, but not associate with a existing child, because if the id in the child Dto is not in the parent's collection, the child is inserted to the database with a new id.
Reference code:
long s1;
long p1, p2;
using (var context = new ApplicationDbContext())
{
var suplier = context.Supliers.Add(new Suplier { Name = "Suplier1" });
var product1 = context.Products.Add(new Product { Name = "Product1" });
var product2 = context.Products.Add(new Product { Name = "Product2" });
suplier.Products.Add(product2);
context.SaveChanges();
s1 = suplier.Id;
p1 = product1.Id;
p2 = product2.Id;
}
Console.WriteLine(String.Join(",", s1, p1, p2));
var suplierDto = new SuplierDto
{
Id = s1,
Name = "This will be updated",
Products = new[]
{
new ProductDto { Id = p1, Name = "This will be inserted with new id" },
new ProductDto { Id = p2, Name = "This will be updated" }
}
};
using (var context = new ApplicationDbContext())
{
context.Supliers.Persist().InsertOrUpdate(suplierDto);
context.SaveChanges();
}
Is there any way of achieving this without having to iterate through the children and manipulating them with the Entity Context?
The included files in the report of #69 have a comment that ExpressionExtentions.GetHashCodeExpression<>
should check for null
to avoid NullReferenceException
when GetHashCode
is invoked.
Probably should be included in
I've updated my project to v6 and made changes for the new syntax. But I'm getting a null pointer exception:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
My code is:
Mapper.Initialize(configuration =>
{
configuration.AddProfile(new AutoMapperProfile());
configuration.AddCollectionMappers();
configuration.SetGeneratePropertyMaps<GenerateEntityFrameworkPrimaryKeyPropertyMaps<ApplicationDbContext>>();
});
The stacktrace is:
at AutoMapper.EquivalencyExpression.EquivalentExpressions.<>c.<CreateEquivalentExpression>b__11_0(PropertyMap pm)
at AutoMapper.EquivalencyExpression.EquivalentExpressions.CreateEquivalentExpression(IEnumerable`1 propertyMaps)
at AutoMapper.EquivalencyExpression.EquivalentExpressions.<>c__DisplayClass7_0.<GetEquivalentExpression>b__1(IGeneratePropertyMaps _)
at AutoMapper.EquivalencyExpression.EquivalentExpressions.<>c__DisplayClass7_0.<GetEquivalentExpression>b__0(TypePair tp)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at AutoMapper.EquivalencyExpression.EquivalentExpressions.GetEquivalentExpression(IConfigurationProvider configurationProvider, TypeMap typeMap)
at AutoMapper.EquivalencyExpression.EquivalentExpressions.GetEquivalentExpression(IConfigurationObjectMapper mapper, Type sourceType, Type destinationType)
at AutoMapper.Mappers.EquivalentExpressionAddRemoveCollectionMapper.IsMatch(TypePair typePair)
at AutoMapper.MapperConfiguration.<>c__DisplayClass54_0.<CoveredByObjectMap>b__0(IObjectMapper m)
at AutoMapper.MapperConfiguration.CoveredByObjectMap(TypePair typePair)
at AutoMapper.MapperConfiguration.GetTypeMap(TypePair initialTypes)
at AutoMapper.LockingConcurrentDictionary`2.<>c__DisplayClass2_1.<.ctor>b__1()
at System.Lazy`1.CreateValue()
at System.Lazy`1.LazyInitValue()
at System.Lazy`1.get_Value()
at AutoMapper.MapperConfiguration.ResolveTypeMap(TypePair typePair)
at AutoMapper.Execution.TypeMapPlanBuilder.MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, TypePair typePair, Expression sourceParameter, Expression contextParameter, PropertyMap propertyMap, Expression destinationParameter)
at AutoMapper.Execution.TypeMapPlanBuilder.CreatePropertyMapFunc(PropertyMap propertyMap)
at AutoMapper.Execution.TypeMapPlanBuilder.TryPropertyMap(PropertyMap propertyMap)
at AutoMapper.Execution.TypeMapPlanBuilder.CreateAssignmentFunc(Expression destinationFunc, Boolean constructorMapping)
at AutoMapper.Execution.TypeMapPlanBuilder.CreateMapperLambda()
at AutoMapper.TypeMap.Seal(IConfigurationProvider configurationProvider)
at AutoMapper.MapperConfiguration.Seal()
at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression configurationExpression)
at AutoMapper.Mapper.Initialize(Action`1 config)
at XXXXXX.UnityConfig.ConfigureAutoMapper(IUnityContainer container) in XXXXXX\App_Start\UnityConfig.cs:line 78
at XXXXXX.UnityConfig.RegisterTypes(IUnityContainer container) in XXXXXX\App_Start\UnityConfig.cs:line 73
at XXXXXX.UnityConfig.<>c.<.cctor>b__5_0() in XXXXXX\App_Start\UnityConfig.cs:line 29
at System.Lazy`1.CreateValue()
Any hints on what I'm doing wrong?
When running .NET Core based xUnit tests in parallel which use AddCollectionMappers()
together with ServiceCollectionExtensions.AddAutoMapper()
i receive sometimes NullReferenceException
:
System.NullReferenceException : Object reference not set to an instance of an object.
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at AutoMapper.EquivalencyExpression.EquivalentExpressions.<>c__DisplayClass5_0`1.<InsertBefore>b__1(IConfigurationProvider c)
at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression configurationExpression)
at AutoMapper.ServiceCollectionExtensions.AddAutoMapperClasses(IServiceCollection services, Action`1 additionalInitAction, IEnumerable`1 assembliesToScan)
at AutoMapper.ServiceCollectionExtensions.AddAutoMapper(IServiceCollection services, Action`1 additionalInitAction, Type[] profileAssemblyMarkerTypes)
I'm sorry but i cannot provide unit test to reproduce the issue - it highly depends on timing.
But looking at AddAutoMapper
and AddCollectionMappers
for me it seems to be caused by static initialization:
AddAutoMapper
internally uses Mapper.Initialize
(statics are initialized multiple times)AddCollectionMappers
internally uses static fields from EquivalentExpressions
which are accessed conurrently during static Mapper.InitializeSo in my opinion we have two issues here:
AddAutoMapper
should not initialize static (as far as i know there is only one Process per parallel running unit tests in one collection). So in fact Mapper.Initialize()
is called multiple times per process.EquivalentExpressions
should use static concurrent dictionaries.Of course, both issues can be worked around disabling parallel unit test execution.
[assembly: CollectionBehavior(DisableTestParallelization = true)]
Say we have
class ADto {public int Id {get; set } }
class A {public int Id {get; set } }
You want to map a collection of ADto's with ids 1,2,3 (for simplicity I am going talk as if the collection was a collection of integers, but they are really the Id of instances of ADto or A)
into a collection of A's that have the ids 1,2
(Assume A is a collection that was loaded using EntityFramework so what we really have in the collection of A's are proxies of A)
When I map the collection using your library I end up having a collection of A with ids 1,2,3 as expected, where 1,2 are the same proxies of A I had before and 3 is an actual instance of A. But 3 also exists in the Database, therefore if I save the collection of A's after mapping I will actually end up duplicating 3.
Is there a way to use a custom Resolver when the dto exists and the domain class instance doesn't?
In this case, I would like to use a custom resolver (which I already have and use for reference properties that reference classes in my domain) that would check if the Id > 0, if so, it would retrieve the instance from the db first and then map from the dto)
Something like this:
.EqualityComparison((dto, model) => dto.Id == model.Id, new MyCustomValueResolver());
I guess this is more of a question than an issue but i was wondering how the collection mapper deals with unloaded entity child collections in entity framework core. I'm interested in using this to replace an injected repository dependency i use to update nested child collections on entities. Up until now i've been manually defining logic for updating unloaded nested child collections for every Domain object i create. It would be much nicer to leverage this if possible to remove this extra set of definitions i have to make to keep child collections updated regardless of if the child collection is loaded or unloaded.
Hi, I'm hoping to use this to avoid having to manually map a fairly deep graph of related entities.
What I'm trying to do though is do a partial update on the top level parent entity i.e a PATCH. So I only want to update the given child items from the left and preserve the other items that weren't specified for updating on the right. Basically the intersection.
Is there already a way to override the behaviour of inserting/removing children in the collection and only map the equivalent ones, update only.
I can get it to map the ones it finds matches for, but it removes the ones not matching in the destination collection, which I don't want to happen. I'm using EF if it helps.
Thanks
I'm looking for some details on the breaking changes for v6.
My following Collections and EF setup is somewhat broken.
private static void ConfigureAutoMapper(IUnityContainer container)
{
var mapperConfiguration = new MapperConfiguration(configurationExpression =>
{
configurationExpression.AddProfile<AutoMapper.Mappers.CollectionProfile>();
configurationExpression.AddProfile(new AutoMapperProfile());
});
IMapper mapper = mapperConfiguration.CreateMapper();
EquivilentExpressions.GenerateEquality.Add(new GenerateEntityFrameworkPrimaryKeyEquivilentExpressions<ApplicationDbContext>(mapper));
container.RegisterInstance<IMapper>(mapper);
}
In particular where is CollectionProfile
and how do I do EquivilentExpressions.GenerateEquality.Add(new GenerateEntityFrameworkPrimaryKeyEquivilentExpressions<ApplicationDbContext>(mapper));
Regards,
Paul
I have a class like this
class MyClass
{
public ICollection<DateTime> Dates{get;set}
}
which I want to map to this:
class RootEntity
{
public ICollection<DateEntity> Dates {get;set;}
}
class DateEntity
{
public DateTime Date {get;set;}
}
To do this I
CreateMap<DateTime, DateEntity>()
.EqualityComparision((d, ef) => d.Date == ef.Date)
which wont compile, since EqualityComparision
has its TSource
and TDestination
constrainted to class
.
Can this constraint be removed without implications? Or, if not, can an alternative be created to allow for a mapping like this?
This error shows up while using AutomapperCollection to update from a DTO to a EF object.
Is Automapper.Collection known to work with EF at all?
I probably have a configuration issue, but I thought I was doing everything "by the book". Until this afternoon.
Here's my basic configuration:
public class MyMappingProfile : Profile
{
public static MapperConfiguration CreateConfiguration()
{
return new MapperConfiguration(cfg =>
{
cfg.AddCollectionMappers();
cfg.SetGeneratePropertyMaps<GenerateEntityFrameworkPrimaryKeyPropertyMaps<MyContext>>();
cfg.AddProfile(new MyMappingProfile());
}
);
}
And here's the situation:
I have collections of child objects that have identifying relationships to the parent. There are also other child objects whose relationship with the parent is not identifying. So I can see what looks like a difference in behavior, beyond what you'd expect from identifying vs. non-identifying relationships.
If the incoming data that goes into AutoMapper
results in no changes to the parent or to any of the child objects, the non-identifying-relationship child objects will remain unchanged in the target data. Their EF DbEntry.State
value remains EntityState.Unchanged
.
But the same situation results in the identifying-relationship child objects all being cleared from the child collection during mapping. Their removal flags them for deletion in the DbContext.ChangeTracker
. (DbEntry.State == EntityState.Deleted)
. (Which I would expect, given that they're in an identifying relationship, but I didn't expect them to be removed from the collection at all.)
Then, presumably as part of mapping, the objects will be re-added. So on SaveChanges
, both the deletes and the inserts will be executed, despite the actual data (including key values!) not changing at all.
Now, I remember reading somewhere that by default, AutoMapper
recreates collections: clearing the target and then re-populating it. This matches what I'm seeing for the identifying-relationship entities. But I understood that AM.Collection
would match the keys and do inserts/updates/deletes instead of this delete-then-insert thing.
Both the identifying-relationship and non-identifying relationship child collections are on the same parent object. So the same MapperConfiguration
instance and even the same Mapper
instance are in use for both sets of child updates, where I'm seeing the differing behavior.
I verified that I am configuring my MapperConfiguration object (calling the method in the code above) plus calling MapperConfiguration.AssertConfigurationIsValid
in all my unit tests. I can trace the MapperConfiguration
object to the point where it gets passed into this code:
var mapper = configuration.CreateMapper();
var persistence = dbContext.Set<TDbElement>().Persist(mapper);
Does this sound like a configuration issue (in AM or EF), or am I stuck with this if I need to use identifying relationships?
As I said, the key values aren't changing. The data doesn't require that those objects be removed. I don't see why the non-key properties couldn't just be updated on the child object (when called for), even if it is in an identifying relationship.
I've got AutoMapper 6.1.1
and AutoMapper.Collection 3.1.1
installed, with EF 6.1.3
. My child object collections are declared as List<T>
, with the virtual
keyword.
Thanks.
Is it possible to get a strong name signed nuget-version?
This is related to a Stack Overflow post, here: https://stackoverflow.com/questions/44509445/automapper-collection-entityframework-causing-invalidoperationexception-after-pe
I copied the source for the InsertOrUpdate
method from your source to my code file, and executed it as a local method. I found that, on inspection, the generated equivalency expression was always true:
Those values in the ToolTip are the IDs of the record I'm looking for, but obviously X == X will always be true. This line:
var to = _sourceSet.FirstOrDefault(equivExpr);
... will return the first record in the set. Since most of the time that's not the record I want, AutoMapper overwrites the Id value, which Entity Framework objects to, which generates the error in the Stack Overflow question referenced above.
(I was wrong when I said, in the question, that this only happened with some entity classes. It happens whenever the first entity in the set doesn't match the one I'm applying InsertOrUpdate
to.)
The configuration from which the mapper instance is generated looks like this:
collectionConfig = new MapperConfiguration(cfg =>
{
cfg.AddCollectionMappers();
cfg.SetGeneratePropertyMaps<GenerateEntityFrameworkPrimaryKeyPropertyMaps<TruckTechContext>>();
cfg.AddProfile(new CollectionProfile(cfg, dbContext));
});
The mapping of the relevant class looks like this:
var vehicleMap = CreateMap<Vehicle, Vehicle>()
.ForMember(veh => veh.LogVehicles, options => options.Ignore())
.ForMember(veh => veh.InspectionReports, options => options.Ignore())
;
The EF context setup looks like this:
modelBuilder.Entity<Vehicle>()
.HasKey(v => v.Id)
.Property(v => v.Id)
.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None);
The definition of the ID field is as simple as it gets:
public int Id { get; set; }
But, if it's relevant, the Id
is actually defined in a base class for the entity rather than in the Vehicle
entity itself.
This is my first time submitting a GitHub issue. Let me know what more you need. Thank you.
Currently, AutoMapper.Collection seems to support .NET Standard, but AutoMapper.Collection.EntityFramework does not. Are there any reasons for this, or would it be possible to update the target versions of the latter package to .NET Standard?
The Fixie test framework is simple and probly flexible, if you learn how to configure it correctly.
Example test inherited from a base class will not be found correctly and the test names is visible on the base class and not on the derived types, this make it harder to use the same test cases with different configurations.
The test framework does not either work with new features in Visual Studio like live unit testing.
Can the test framework be changed to xUnit that have more capabilities and activly developed?
Using AutoMapper and AutoMapper.Collection everything works with AutoMapper 6.2.0. After upgrading to AutoMapper 6.2.1 AutoMapper.Collection is throwing a Null Reference Exception. Originally created issue for AutoMapper as its update is what is causing the issue (AutoMapper/AutoMapper#2415) but was requested to open it here it instead.
System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=AutoMapper.Collection
StackTrace:
at AutoMapper.EquivalencyExpression.EquivalentExpressions.<>c.<CreateEquivalentExpression>b__11_0(PropertyMap pm)
at System.Linq.Enumerable.Any[TSource](IEnumerable`1 source, Func`2 predicate)
at AutoMapper.EquivalencyExpression.EquivalentExpressions.CreateEquivalentExpression(IEnumerable`1 propertyMaps)
at AutoMapper.EquivalencyExpression.EquivalentExpressions.<>c__DisplayClass7_0.<GetEquivalentExpression>b__1(IGeneratePropertyMaps _)
at System.Linq.Enumerable.SelectListIterator`2.MoveNext()
at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Func`2 predicate, Boolean& found)
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
at AutoMapper.EquivalencyExpression.EquivalentExpressions.<>c__DisplayClass7_0.<GetEquivalentExpression>b__0(TypePair tp)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at AutoMapper.EquivalencyExpression.EquivalentExpressions.GetEquivalentExpression(IConfigurationProvider configurationProvider, TypeMap typeMap)
at AutoMapper.EquivalencyExpression.EquivalentExpressions.GetEquivalentExpression(IConfigurationObjectMapper mapper, Type sourceType, Type destinationType)
at AutoMapper.Mappers.EquivalentExpressionAddRemoveCollectionMapper.IsMatch(TypePair typePair)
at AutoMapper.MapperConfiguration.<>c__DisplayClass63_0.<FindMapper>b__0(IObjectMapper m)
at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Func`2 predicate, Boolean& found)
at AutoMapper.MapperConfiguration.FindMapper(TypePair types)
at AutoMapper.MapperConfiguration.GetTypeMap(TypePair initialTypes)
at AutoMapper.LockingConcurrentDictionary`2.<>c__DisplayClass2_1.<.ctor>b__1()
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
at System.Lazy`1.CreateValue()
at AutoMapper.MapperConfiguration.ResolveTypeMap(TypePair typePair, ITypeMapConfiguration inlineConfiguration)
at AutoMapper.MapperConfiguration.ResolveTypeMap(TypePair typePair)
at AutoMapper.Execution.TypeMapPlanBuilder.ResolveTypeMap(TypePair types)
at AutoMapper.Execution.TypeMapPlanBuilder.ResolvePropertyTypeMap(PropertyMap propertyMap)
at AutoMapper.Execution.TypeMapPlanBuilder.<CheckForCycles>b__17_1(PropertyMap pm)
at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at AutoMapper.Execution.TypeMapPlanBuilder.CheckForCycles(Stack`1 typeMapsPath)
at AutoMapper.Execution.TypeMapPlanBuilder.CreateMapperLambda(Stack`1 typeMapsPath)
at AutoMapper.TypeMap.Seal(IConfigurationProvider configurationProvider, Stack`1 typeMapsPath)
at AutoMapper.MapperConfiguration.Seal()
at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression configurationExpression)
at AutoMapper.Mapper.Initialize(Action`1 config)
at MyApp.API.ViewModels.Mappings.AutoMapperConfiguration.Configure(IServiceProvider provider) in C:\dev\myApp-api\MyApp.API\ViewModels\Mappings\AutoMapperConfiguration.cs:line 16
at MyApp.API.Startup.ConfigureServices(IServiceCollection services) in C:\dev\myApp-api\MyApp.API\Startup.cs:line 218
I'm trying to upgrade my project from AutoMapper 4.1.1 / AutoMapper.Collection 1.1.2 to AutoMapper 5.0.2 / AutoMapper.Collection 2.0.1. I have a scenario which works fine with old dlls and I get unexpected null as a result of mapping with new dlls.
Is this a bug or I am missing something?
Tried installing with version 5.0.2 of AutoMapper. Seems to be failing due to inaccessibility issues, such as TypeHelper and TypeExtensions.
Hi, I just checked your code and I really like it, I just don't understand why you need the "second" mapper (ObjectToEquivalencyExpressionByEquivalencyExistingMapper),
can you enlighten me? Thx for the great extension, it's really useful!!
Does AutoMapper Collection EntityFramework supports EF Core, installing it in my project gives me incompatibility issues,
if not what is the alternative ?
warning NU1701: Package 'AutoMapper.Collection.EntityFramework 3.1.3' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v2.1'. This package may not be fully compatible with your project.
Mapping one object to another using same class to another where a property Statuses was defined but the value was null now results in an exception as it appear automapper is now mapping null values.
"System.Web.Mvc.SelectList needs to have a constructor with 0 args or only optional args. Parameter name: type"
Mapping Definition
cfg.CreateMap<Areas.LOA.Models.NonSalesEventBaseSearchView, Areas.LOA.Models.NonSalesEventBaseSearchView>()
.ForMember(x => x.Prop1 , opt => opt.Ignore())
.ForMember(x => x.Prop2, opt => opt.Ignore())
.ForMember(x => x.Prop3, opt => opt.Ignore());
Mapping Line..
AutoMapper.Mapper.Map<NonSalesEventBaseSearchView,NonSalesEventBaseSearchView>(previousSearchCriteria, viewModel);
Class
NonSalesEventBaseSearchView
public SelectList Statuses { get; set; }
Hi,
I have been experiencing errors trying to create a map between an inmutable object value and an entity.
public class Charge
{
public Charge(string category, string description, decimal value)
{
Category = category;
Description = description;
Value = value;
}
public string Category { get; }
public string Description { get; }
public decimal Value { get; }
public override string ToString()
{
return $"{Category}|{Description}|{Value}";
}
public override int GetHashCode()
{
return $"{Category}|{Description}|{Value}".GetHashCode();
}
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
{
return true;
}
if (ReferenceEquals(null, obj))
{
return false;
}
var _obj = obj as Charge;
if (_obj == null)
{
return false;
}
return Category == _obj.Category && Description == _obj.Description && Value == _obj.Value;
}
}
public class SaleCharge
{
public Guid SaleId { get; set; }
public string Category { get; set; }
public string Description { get; set; }
public decimal Value { get; set; }
public virtual Sale Sale { get; set; }
}
CreateMap<Charge, SaleCharge>()
.ForMember(d => d.Sale, o => o.Ignore())
.ForMember(d => d.SaleId, o => o.Ignore())
.EqualityComparison((c, sc) => sc.Category == c.Category && sc.Description == c.Description);
CreateMap<SaleCharge, Charge>()
.ConstructUsing(
(saleCharge => new Charge(saleCharge.Category, saleCharge.Description, saleCharge.Value)))
.EqualityComparison((sc, c) => sc.Category == c.Category && sc.Description == c.Description);
Unhandled Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentException: Method 'Int32 GetHashCode()' declared on type 'Leaderboard.Sales.Charge' cannot be called with instance of type 'System.String'
at System.Linq.Expressions.Expression.ValidateCallInstanceType(Type instanceType, MethodInfo method)
at System.Linq.Expressions.Expression.ValidateStaticOrInstanceMethod(Expression instance, MethodInfo method)
at System.Linq.Expressions.Expression.ValidateMethodAndGetParameters(Expression instance, MethodInfo method)
at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method)
at AutoMapper.EquivalencyExpression.ExpressionExtentions.GetHashCodeExpression[T](List`1 members, ParameterExpression sourceParam)
at AutoMapper.EquivalencyExpression.EquivalentExpression`2..ctor(Expression`1 equivalentExpression)
at AutoMapper.EquivalencyExpression.EquivalentExpressions.EqualityComparison[TSource,TDestination](IMappingExpression`2 mappingExpression, Expression`1 EquivalentExpression)
at Leaderboard.Sales.API.Mappings.DefaultProfile..ctor() in /home/kellerman/Repos/Leaderboard 2/Services/Sales/Leaderboard.Sales.API/Mappings/DefaultProfile.cs:line 23
--- End of inner exception stack trace ---
at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at System.Activator.CreateInstance(Type type)
at AutoMapper.Configuration.MapperConfigurationExpression.AddProfile(Type profileType)
at AutoMapper.ServiceCollectionExtensions.<>c__DisplayClass16_0.<AddAutoMapperClasses>b__4(IMapperConfigurationExpression cfg)
at AutoMapper.MapperConfiguration.Build(Action`1 configure)
at AutoMapper.Mapper.Initialize(Action`1 config)
at AutoMapper.ServiceCollectionExtensions.AddAutoMapperClasses(IServiceCollection services, Action`1 additionalInitAction, IEnumerable`1 assembliesToScan)
at AutoMapper.ServiceCollectionExtensions.AddAutoMapper(IServiceCollection services, Action`1 additionalInitAction)
at Leaderboard.Sales.API.Startup.ConfigureServices(IServiceCollection services) in /home/kellerman/Repos/Leaderboard 2/Services/Sales/Leaderboard.Sales.API/Startup.cs:line 47
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
at Leaderboard.Sales.API.Program.Main(String[] args) in /home/kellerman/Repos/Leaderboard 2/Services/Sales/Leaderboard.Sales.API/Program.cs:line 14
If I remove the GetHashCode on Charge value object then I got not error at all. But I think this is a bug, so I'm filling the issue.
Thank you.
Tried to keep this simple so hopefully it's clear:
DB Forest: I have a Forest which has a Tree in it. There are Sections in my Forest, but these Sections currently have no Trees in them.
Changed Forest: I simply add the Tree object to one of the Sections (there is only one Tree instance still)
This illustrates the small change: https://imgur.com/a/hahD7
I load the DB Forest from the database, and map the Changed Forest to the DB Forest.
I want Entity Framework to automatically detect that a tree has been added to the section, which it does, but it detects it as a newly created tree instance (rather than use the existing object). Thus resulting in duplicate trees.
When I map the objects, I try to tell AutoMapper.Collection to detect this equivalency and not create any new objects, but use the existing ones:
var config = new MapperConfiguration(cfg => {
cfg.AddCollectionMappers();
cfg.CreateMap<Forest, Forest>().PreserveReferences().EqualityComparison((s, d) => s.ID == d.ID);
cfg.CreateMap<Tree, Tree>().PreserveReferences().EqualityComparison((s, d) => s.ID == d.ID);
cfg.CreateMap<Section, Section>().PreserveReferences().EqualityComparison((s, d) => s.ID == d.ID);
});
.
The IDs of both trees are identical, but AutoMapper still creates 2 separate tree objects. How might I prevent this? Many thanks.
(EF 6.1.3, AutoMapper 6.0.2, AutoMapper.Collection 3.1.2)
It'd be great if we could use this library in ASP.NET Core. I tried adding the package via .NET CLI, and even though I didn't get any compatibility issues, I couldn't find EqualityComparison() method when creating maps in a Profile.
I'm using Automapper with a container.
I have just added Automapper.Collection.
Adding the following line to the configuration
EquivilentExpressions.GenerateEquality.Add( new GenerateEntityFrameworkPrimaryKeyEquivilentExpressions<MyContext>() );
Leads to the following exception:
"Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance."
How can i avoid the static call to EquivilentExpression.GenerateEquality.Add?
3.1.0 introduced a new internal Hash Code generation.
This breaks the following code that is working 3.0.1
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddCollectionMappers();
cfg.CreateMap<ClientDto, Client>()
.EqualityComparison((src, dest) => dest.DtoId == 0 ? src.Code == dest.Code : src.Id == dest.DtoId); // does not work in 3.1.0 but in 3.0.1
//.EqualityComparison((src1, dest1) => ((Func<ClientDto, Client, bool>)((ClientDto src, Client dest) => dest.DtoId == 0 ? src.Code == dest.Code : src.Id == dest.DtoId)).Invoke(src1, dest1)); // works in 3.1.0 and 3.0.1
});
IMapper mapper = new Mapper(config);
// Arrange
var dto = new ClientDto
{
Code = "abc",
Id = 1
};
var entity = new Client { Code = dto.Code, Id = 42 };
var entityCollection = new List<Client>();
entityCollection.Add(entity);
// Act
mapper.Map(new[] { dto }, entityCollection);
// Assert
Console.WriteLine($"entity == entityCollection[0]: {entity == entityCollection[0]}");
Console.WriteLine("If false is displayed above, the entity was not updated, but replaced.");
Console.ReadKey();
}
}
public class Client
{
public long Id { get; set; }
public string Code { get; set; }
public long DtoId { get; set; }
}
public class ClientDto
{
public long Id { get; set; }
public string Code { get; set; }
}
You use the world 'persistance' in your code and for file names but it is 'persistence' not 'persistance'
We just updated AutoMapper from version 6.1.1 to 6.2.0. I also tried the more recently released 6.2.1 and the issue still persists.
As soon as we did we not get System.ArgumentException: 'Argument types do not match' on application startup.
at System.Linq.Expressions.Expression.Condition(Expression test, Expression ifTrue, Expression ifFalse)
at AutoMapper.Mappers.EquivalentExpressionAddRemoveCollectionMapper.MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression)
at AutoMapper.Execution.ExpressionBuilder.MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, TypePair typePair, Expression sourceParameter, Expression contextParameter, PropertyMap propertyMap, Expression destinationParameter)
at AutoMapper.Execution.TypeMapPlanBuilder.CreatePropertyMapFunc(PropertyMap propertyMap, Expression destination)
at AutoMapper.Execution.TypeMapPlanBuilder.TryPropertyMap(PropertyMap propertyMap)
at AutoMapper.Execution.TypeMapPlanBuilder.CreateAssignmentFunc(Expression destinationFunc, Boolean constructorMapping)
at AutoMapper.Execution.TypeMapPlanBuilder.CreateMapperLambda(Stack1 typeMapsPath) at AutoMapper.TypeMap.Seal(IConfigurationProvider configurationProvider, Stack
1 typeMapsPath)
at AutoMapper.MapperConfiguration.Seal()
at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression configurationExpression)
at TestApp.Startup.ConfigureServices(IServiceCollection services) in C:\Users\natha\source\repos\TestApp\TestApp\Startup.cs:line 115
It is thrown from this code
var mapperConfiguration = new MapperConfiguration(_ =>
{
_.AddCollectionMappers();
_.AddProfile<DefaultProfile>();
});
I've tried looking up how to resolve this, but found nothing. I don't understand what's causing it. Everything worked before updating.
I made a new repo demonstrating the problem here.
The project that demonstrates the issue is a cut down version containing parts of the original application we first witnessed the issue happening. All of the internal nuget packages not available publicly have been converted to projects within the solution to be referenced.
The project TestApp should be set to be the startup project. Within TestApp.csproj you will see the nuget package reference to AutoMapper 6.2.1. Left in place the exception will be thrown on startup. If changed to 6.1.1, the application starts normally.
I feel this might be related to AutoMapper.Collection as if I remove the lines
_.AddCollectionMappers();
and lines like this from the mapping Profile
.EqualityComparison((source, target) => source.SectionId == target.SectionId)
then application starts. But without the collection equality comparisons, the mapping does not work correctly.
I'm trying to map a complex object to another, without creating new instances of the nested objects. (in my case, so EF's ChangeTracker won't mark them all as new entities, or modified entities unless they have changed)
When I do this:
var config = new MapperConfiguration(cfg =>
{
cfg.AddCollectionMappers();
cfg.CreateMap<Project, Project>().EqualityComparison((s, d) => s.ID == d.ID);
cfg.CreateMap<SomeNestedObject, SomeNestedObject>().EqualityComparison((s, d) => s.ID == d.ID);
cfg.CreateMap<SomeOtherNestedObject, SomeOtherNestedObject>().EqualityComparison((s, d) => s.ID == d.ID);
});
existingProject = config.CreateMapper().Map(changedProject, existingProject);
Every nested object within the Project is marked as modified in EF's ChangeTracker (even though I've only changed just one property in one of the nested objects). Does AutoMapper.Collection still change the object in some way, even if the properties on both source and destination are identical? (which would therefore cause EF to think something has changed)
Reference: https://stackoverflow.com/questions/45590265/automapper-confuses-changetracker
Testing the Automapper.Collection with EF6 the ability to remove objects from a collection.
Let's say that the collection started with a record, and I'm mapping an empty collection (count=0) to the EF collection. It seems to map properly, but when I go to save it, I get the following error:
An unhandled exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll
Additional information: The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
This is my code:
Imports AutoMapper
Imports AutoMapper.Mappers
Imports AutoMapper.Collection
Imports AutoMapper.EntityFramework
Imports AutoMapper.EquivilencyExpression
Public Class QuoteModel
Public Property ID As Integer
Public Property Quote_Lines As List(Of Quote_LinesDTO)
Public Sub New()
Me.Quote_Lines = New List(Of Quote_LinesDTO)
End Sub
End Class
Public Class Quote_LinesDTO
Public Property ID As Integer
Public Property Line_ID As Integer
Public Property Date_Created As DateTime
Public Property User_Created_ID As Guid
Public Property GlobalID As Guid
End Class
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Using theContext As New FleetFusionEntities1
Dim theQuote As Quote = theContext.Quotes.Where(Function(ent) ent.ID = 151709).SingleOrDefault
Dim theQuoteModel As New QuoteModel
theQuoteModel.ID = 151709
'theQuoteModel.Quote_Lines.Add(New Quote_LinesDTO With {.ID = 5, .Line_ID = 2, .Date_Created = Now, .GlobalID = Guid.NewGuid, .User_Created_ID = Guid.NewGuid})
Dim MapConfig As New AutoMapper.MapperConfiguration(Sub(cfg) cfg.AddProfile(Of Automapper_Profile)())
Dim Mapper As AutoMapper.IMapper = New AutoMapper.Mapper(MapConfig)
Mapper.Map(theQuoteModel, theQuote)
theContext.SaveChanges()
End Using
End Sub
End Class
Public Class Automapper_Profile
Inherits AutoMapper.Mappers.CollectionProfile
Public Sub New()
CreateMap(Of QuoteModel, Quote)()
CreateMap(Of Quote_LinesDTO, Quote_Lines).EqualityComparision(Function(dto, ent) dto.ID = ent.ID)
'CreateMap(Of Quote_Lines, Quote_Lines).EqualityComparision(Function(dto, ent) dto.ID = ent.ID) _
' .ForMember(Function(ent) ent.Quote, Sub(opts) opts.Ignore()) _
' .ForMember(Function(ent) ent.Quote_ID, Sub(opts) opts.Ignore())
End Sub
End Class`
i'm using Automapper in .net Core Web Application. I,m trying to map a List to an other one it dosen't ignore member that i select in Formember but when i map an object to object it works fine!
Code:
var mapperConfig = new MapperConfiguration(a =>
{
var b = a.CreateMap<SmsContact, SmsContactViewModel>()
.IgnoreAllPropertiesWithAnInaccessibleSetter()
.IgnoreAllSourcePropertiesWithAnInaccessibleSetter();
b.ForMember(v => v.PhoneNumber, v => v.Ignore());
});
var destModel = _smsContactService.GetAll(a => true).ToList();
var m=mapperConfig.CreateMapper().Map(editModel, destModel);
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.