Giter Site home page Giter Site logo

automapper.collection's Introduction

AutoMapper

AutoMapper.Collection

Adds ability to map collections to existing collections without re-creating the collection object.

Will Add/Update/Delete items from a preexisting collection object based on user defined equivalency between the collection's generic item type from the source collection and the destination collection.

NuGet

How to add to AutoMapper?

Call AddCollectionMappers when configuring

Mapper.Initialize(cfg =>
{
    cfg.AddCollectionMappers();
    // Configuration code
});

Will add new IObjectMapper objects into the master mapping list.

Adding equivalency between two classes

Adding equivalence to objects is done with EqualityComparison extended from the IMappingExpression class.

cfg.CreateMap<OrderItemDTO, OrderItem>().EqualityComparison((odto, o) => odto.ID == o.ID);

Mapping OrderDTO back to Order will compare Order items list based on if their ID's match

Mapper.Map<List<OrderDTO>,List<Order>>(orderDtos, orders);

If ID's match, then AutoMapper will map OrderDTO to Order

If OrderDTO exists and Order doesn't, then AutoMapper will add a new Order mapped from OrderDTO to the collection

If Order exists and OrderDTO doesn't, then AutoMapper will remove Order from collection

Why update collection? Just recreate it

ORMs don't like setting the collection, so you need to add and remove from preexisting one.

This automates the process by just specifying what is equal to each other.

Can it just figure out the ID equivalency for me in Entity Framework?

Automapper.Collection.EntityFramework or Automapper.Collection.EntityFrameworkCore can do that for you.

Mapper.Initialize(cfg =>
{
    cfg.AddCollectionMappers();
    cfg.SetGeneratePropertyMaps<GenerateEntityFrameworkPrimaryKeyPropertyMaps<DB>>();
    // Configuration code
});

User defined equality expressions will overwrite primary key expressions.

What about comparing to a single existing Entity for updating?

Automapper.Collection.EntityFramework does that as well through extension method from of DbSet.

Translate equality between dto and EF object to an expression of just the EF using the dto's values as constants.

dbContext.Orders.Persist().InsertOrUpdate<OrderDTO>(newOrderDto);
dbContext.Orders.Persist().InsertOrUpdate<OrderDTO>(existingOrderDto);
dbContext.Orders.Persist().Remove<OrderDTO>(deletedOrderDto);
dbContext.SubmitChanges();

Note: This is done by converting the OrderDTO to Expression<Func<Order,bool>> and using that to find matching type in the database. You can also map objects to expressions as well.

Persist doesn't call submit changes automatically

Where can I get it?

First, install NuGet. Then, install AutoMapper.Collection from the package manager console:

PM> Install-Package AutoMapper.Collection

Additional packages

AutoMapper Collection for Entity Framework

PM> Install-Package AutoMapper.Collection.EntityFramework

AutoMapper Collection for Entity Framework Core

PM> Install-Package AutoMapper.Collection.EntityFrameworkCore

AutoMapper Collection for LinqToSQL

PM> Install-Package AutoMapper.Collection.LinqToSQL

automapper.collection's People

Contributors

bzbetty avatar elbedeawi avatar fubar-coder avatar jahav avatar jbogard avatar lbargaoanu avatar nphmuller avatar say25 avatar slubowsky avatar tasteful avatar tylercarlson1 avatar wertzui avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

automapper.collection's Issues

Duplicate instances despite EqualityComparison

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)

Support for many to many relationships

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?

Options is not passed down the object graph when mapping.

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; }
}

Does not recognize AddCollectionMappers

I try to use automapper.collection but in the Mapper.Initialize method the AddCollectionMappers method is not recognized

I'm using .net 4.7, automapper 6.1.1, automapper.collection 3.1.1

Thank you.
c

Speculations on How Collections Get Mapped

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!

Fixing Typos?

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?

Support for KeyAttribute

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.

Child objects with identifying-relationship keys are deleted and then re-added to collections, causing DB delete/insert on SaveChanges

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.

EF ChangeTracker marks all entities as State.Modified after mapping

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

GenerateEntityFrameworkPrimaryKeyPropertyMaps throws null pointer exception (v6.0.0)

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?

.Persist().InsertOrUpdate() generates equivalency expression that is always true

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:

image

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.

Remove class constraint for EqualityComparision

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?

Upgrading AutoMapper from 6.2.0 to 6.2.1 get null reference exception on mapper initialization

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

Inheritance with collections no longer working

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.
issue_ii

And if you put the .EqualityComparison() only in your base mapping it isn't used at all.
equalitycomparisonnotused
)

It would be great if you can have a look and hopefully fix the issue.

Br
Renรจ
AutoMapper.CollectionIssue.zip

Change unit test framwork

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?

EquivlentExpression-mapper should not check for null-values in IsMatch-method

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.

Exception thrown with custom GetHashCode() implementation on Value Objects.

Hi,
I have been experiencing errors trying to create a map between an inmutable object value and an entity.

Charge Inmutable Object

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;
        }
    }

Entity

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; }                
    }

Mappings

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);    

Runtime Error

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.

Swapping object from 1 list to another

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?

Support .NET Standard in all packages

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?

Array support

Either support arrays or change README.md to List<T> accordingly.

Expected behavior

AutoMapper.Collection works with arrays as indicated in README.md

Actual behavior

AutoMapper.Collection works only with List<T>

Steps to reproduce

Modify test cases to use arrays instead of lists, see MapCollectionWithEqualityTests.cs in AutoMapper.Collection.Tests project.

Feature: Have a callback for removed items

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.

3.1.0 Hash generation broke entity updating

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; }
    }

How to avoid static EquivilentExpression.GenerateEquality.Add call in order to work with a container

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?

NullReferenceException running .NET Core based xUnit tests in parallel when using AddCollectionMappers() and ServiceCollectionExtensions.AddAutoMapper() together

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.Initialize

So 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)]

Update to AM 6.2.2 from 5.5.0 gives exception: ValueFactory attempted to access the Value property of this instance.

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.

Error handling with invalid DbContext

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 :)

Info on v6 breaking changes

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

When upgrading from 6.1.1 to 6.2.1 there appears to be an issue with null properties which are now being mapped resulting in exceptions.

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; }

Collection clear with equalitycomparision

Source/destination types

    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
    {

    }

Mapping configuration

services.AddAutoMapper(c => { c.AddCollectionMappers(); c.AddProfile<MyMapperProfile>(); });
[...]
 CreateMap<GroupPerUser, GroupPerUser>()
                .EqualityComparison((g1, g2) => g1.UserProfileId == g2.UserProfileId && g1.GroupId == g2.GroupId);

Version: 6.2.2

Expected behavior

From my understanding using AddCollectionMappers collections have to be updated depending on equality comparision.

Actual behavior

In this case i see that collection is first cleared then filled. This produce an issue with EF changeTracker.

Steps to reproduce

      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);
            

Moving objects between lists

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!

EF Core support for AutoMapper.Collection.EntityFramework

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.

EquivalentExpressions.EquivalentExpressionDictionary is not thread safe

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());
}

Attempting to use this with EF 6

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`

Spelling

You use the world 'persistance' in your code and for file names but it is 'persistence' not 'persistance'

Override collection insert/delete behaviour

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

Exception on startup - Additional information: variable 'src' of type 'InvoiceModel' referenced from scope '', but it is not defined

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();

        }
    }
}

How to eager load with Persist?

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?

Upgrade to AutoMapper 5.0.2 issue

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.

Gist with issue code.

Is this a bug or I am missing something?

.net core un loaded child collections.

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.

it does not work as expected!

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);
    }
}

}

AutoMapper 6.2.* + AutoMapper.Collection => System.ArgumentException: 'Argument types do not match'

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, Stack1 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.

How to Ignore a Member In List To List Mapping In C#?

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);

Support for .NET Core

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.

Using a Custom Value Resolver when a DTO is found and the domain class instance is not

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());

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.