AutoMapper Entity Framework Proxy Inherited Class Mapping Issue - entity-framework

Have come across an issue with AutoMapper (v9.0) using the incorrect mapping for an inherited class when mapping to an Entity Framework (v6.4) proxy class. It appears to be related to the order in which the mapping is executed, and seems to be related to some kind of caching of the maps used. Here is the Entity Framwork configuration:
public class MyDbContext : DbContext
{
public MyDbContext()
{
base.Configuration.ProxyCreationEnabled = true;
}
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public class Blog
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
}
public class Post
{
[Key]
public int Id { get; set; }
public DateTime PostDate { get; set; }
public string Content { get; set; }
public string Keywords { get; set; }
public virtual Blog Blog { get; set; }
}
And my DTO classes:
public class PostDTO
{
public DateTime PostDate { get; set; }
public string Content { get; set; }
}
public class PostWithKeywordsDTO : PostDTO
{
public string Keywords { get; set; }
}
Mapping profile:
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<PostDTO, Post>()
.ForMember(dest => dest.Keywords, opt => opt.MapFrom(src => "No Keywords Specified"));
CreateMap<PostWithKeywordsDTO, Post>()
.ForMember(dest => dest.Keywords, opt => opt.MapFrom(src => src.Keywords));
}
}
I'm attempting to map these DTO object onto a proxy of the 'Post' class which is generated either by fetching an existing Post record from the database or by creating a new proxy of the Post class using (note, I need to enable the proxy class creation for performance reasons in my app):
_myDbContext.Posts.Create();
Now, when I attempt to perform a map from the following postDTO and postWithKeywordsDTO objects to the proxy class:
var postDTO = new PostDTO
{
PostDate = DateTime.Parse("1/1/2000"),
Content = "Post #1"
};
var postWithKeywordsDTO = new PostWithKeywordsDTO
{
PostDate = DateTime.Parse("6/30/2005"),
Content = "Post #2",
Keywords = "C#, Automapper, Proxy"
};
var postProxy = mapper.Map(postDTO, _myDbContext.Posts.Create());
var postWithKeywordsProxy = mapper.Map(postWithKeywordsDTO, dbContext.Posts.Create());
the resulting proxy objects are (pseudo-json):
postProxy: {
PostDate: '1/1/2000',
Content: 'Post #1',
Keywords: 'No Keywords Specified'
}
postWithKeywordsProxy: {
PostDate: '6/30/2005',
Content: 'Post #2',
Keywords: 'No Keywords Specified'
}
Furthermore, if I use something like an inline ValueResolver in the mapping and put a breakpoint on the 'return' lines, I can see that the PostDTO -> Post mapping is being used in both cases, and the PostWithKeywords -> Post mapping isn't being hit at all.
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<PostDTO, Post>()
.ForMember(dest => dest.Keywords, opt => opt.MapFrom(src => "No Keywords Specified"))
.ForMember(dest => dest.Content, opt => opt.MapFrom((src, dest) =>
{
return src.Content; <-- Hit for both PostDTO and PostWithKeywordsDTO maps to Post
}))
;
CreateMap<PostWithKeywordsDTO, Post>()
.ForMember(dest => dest.Keywords, opt => opt.MapFrom(src => src.Keywords))
.ForMember(dest => dest.Content, opt => opt.MapFrom((src, dest) =>
{
return src.Content;
}))
;
}
}
What I take from this is that it appears that there is some kind of issue in identifying which Type Map to use when dealing with a Proxy object. It seems as though in the first scenario, it encounters an attempted map between PostDTO -> Post_B24121DF0B3091C6258AA7C620C6D74F4114A74351B7D90111C87EAAF182C939 (proxy class), and correctly determines that the map to use is the PostDTO -> Post mapping. It then encounters the attempted map between PostWithKeywordsDTO -> Post_B24121DF0B3091C6258AA7C620C6D74F4114A74351B7D90111C87EAAF182C939 and doesn't realize that the PostWithKeywordsDTO is actually a child of PostDTO, and mistakenly re-uses the PostDTO -> Post mapping.
What's odd, however, is what happens if I reverse the order in which the maps are executed:
var postWithKeywordsProxy = mapper.Map(postWithKeywordsDTO, dbContext.Posts.Create());
var postProxy = mapper.Map(postDTO, _myDbContext.Posts.Create());
the resulting proxy objects are correct:
postWithKeywordsProxy: {
PostDate: '6/30/2005',
Content: 'Post #2',
Keywords: 'C#, Automapper, Proxy'
}
postProxy: {
PostDate: '1/1/2000',
Content: 'Post #1',
Keywords: 'No Keywords Specified'
}
This makes me think it has to do with some kind of caching mechanism, which possibly looks for the first map it can find which satisfies the requested proxy map, even if it's not an exact match. In this case, the PostWithKeywordsDTO -> Post_B24121DF0B3091C6258AA7C620C6D74F4114A74351B7D90111C87EAAF182C939 mapping happens first, such that when the subsequent PostDTO -> Post_B24121DF0B3091C6258AA7C620C6D74F4114A74351B7D90111C87EAAF182C939 map happens, it's not able to find a cached Type Map which satisfies the parameters and it continues with generating the correct cached map.
I did attempt to use the version of the Map method which takes in the explicit types of the items to be mapped, however this produced the same result:
var postProxy = mapper.Map(postDTO, _myDbContext.Posts.Create(), typeof(PostDTO), typeof(Post));
var postWithKeywordsProxy = mapper.Map(postWithKeywordsDTO, dbContext.Posts.Create(), typeof(PostWithKeywordsDTO), typeof(Post));
Also note that if I don't use the proxy versions of the Post class, everything works as expected, so it doesn't appear to be an issue w/ the mapping configuration.
As for possible workarounds, the closest I've found is in this thread (Automapper : mapping issue with inheritance and abstract base class on collections with Entity Framework 4 Proxy Pocos), which appears to be a similar issue, however the workaround in this case was to use the 'DynamicMap' function, which has since been deprecated in AutoMapper. Has anyone else encountered a similar issue w/ proxy class mapping and know of another solution?

Here's what I ended up doing to solve the issue. After digging in the code for a bit, I decided that my solution to force the mapping types was going to cause other issues with mapping inheritance.
Instead, I settled on a solution which computes the 'distance' each matching type map is from the requested types based on the number of inheritance levels the type map source/destination types are from the corresponding requested types, and selects the 'closest' one. It does this by treating the 'Source Distance' as the x value and the 'Destination Distance' as the y value in the standard two coordinate distance calculation:
Overall Distance = SQRT([Source Distance]^2 + [Destination Distance]^2)
For example, in my scenario I have the following maps:
PostDTO -> Post
and
PostWithKeywordsDTO -> Post
When attempting to map a PostWithKeywordsDTO -> PostProxy there is no exact mapping match, so we have to determine which map is the best fit. In this case, the list of possible maps which can be used are:
PostDTO -> Post (Since PostWithKeywordsDTO inherits from PostDTO and PostProxy inherits from Post)
or
PostWithKeywordsDTO -> Post (Since PostProxy inherits from Post)
To determine which map to use, it calculates:
PostDTO -> Post:
Source Distance = 1 (PostDTO is one level above PostWithKeywordsDTO)
Destination Distance = 1 (Post is one level above PostProxy)
Overall Distance = 1.414
PostWithKeywordsDTO -> Post
Source Distance = 0 (since PostWithKeywordsDTO = PostWithKeywordsDTO)
Destination Distance = 1 (Post is one level above PostProxy)
Overall Distance = 1
So in this case, it would use the PostWithKeywordsDTO -> Post mapping, since the distance is the smallest. This appears to work in all cases, and satisfies all of the AM unit tests as well. Here's a gist of the updates needed to the code (although I'm sure there are probably cleaner/more efficient ways to do it).

Related

Save document with the member number instead the name with protobuf-net and MongoDB

I saw somewhere that with the Go MongoDB driver it is possible to save a document with the order number instead of the field name.
They end up with this in the database:
{
"3": "foo",
"10": 1,
"33": 123456
"107": {
"2": "bar",
"1": "foo"
}
}
I like the idea!
So, I tried to find a way to do the same with the MongoDB C# driver.
I have the code below but I am not sure what I should bring from the protobut-net to get the member order number.
var pack = new ConventionPack();
pack.AddMemberMapConvention("numbered", m => m.SetElementName( WHAT TO PUT HERE ));
ConventionRegistry.Register("numbered", pack, type => true);
The SetElementName takes a string parameter.
How can I grab the order number of a member from protobuf-net?
Something like ...Member.Order.ToString()
I don't know if this whole thing is a great idea but I want to test it.
Thanks
-- UPDATE --
Just to add more information. I am using inheritance for my models to use generics.
[BsonDiscriminator("Base", RootClass = true)]
[DataContract]
public abstract class Base
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[ProtoMember(1)]
public string Id { get; set; }
[BsonDateTimeOptions]
[ProtoMember(2)]
public DateTime CreatedDate { get; private set; } = DateTime.UtcNow;
[BsonDateTimeOptions]
[ProtoMember(3)]
public DateTime UpdatedDate { get; set; } = DateTime.UtcNow;
}
[ProtoContract]
public class Todo : Base
{
[ProtoMember(10)]
public string Title { get; set; }
[ProtoMember(20)]
public string Content { get; set; }
[ProtoMember(30)]
public string Category { get; set; }
}
And I added this line as shown in the protobuf-net documentation:
RuntimeTypeModel.Default[typeof(Base)].AddSubType(42, typeof(Todo));
So with that and what Marc showed to get the member's number, I end up having a custom Convention Class in MongoDB with <T> so I can use it for other objects:
public class NumberedElementNameConvention<T> : ConventionBase, IMemberMapConvention where T : Base
{
public void Apply(BsonMemberMap memberMap)
{
var members = RuntimeTypeModel.Default[typeof(T)].GetFields();
foreach (var member in members)
{
memberMap.SetElementName(member.FieldNumber.ToString());
}
}
}
And the registration of this Convention is done like so:
var pack = new ConventionPack { new NumberedElementNameConvention<Todo>() };
ConventionRegistry.Register("NumberedName", pack, type => true);
After running this I get this error:
Grpc.AspNetCore.Server.ServerCallHandler[6]
Error when executing service method 'CreateOne'.
MongoDB.Bson.BsonSerializationException: The property 'UpdatedDate' of type 'Nnet.Models.Base' cannot use element name '30' because it is already being used by property 'CreatedDate'...
Also, when I run the code below I am expecting to get all members of the Todo object.
var members = RuntimeTypeModel.Default[typeof(Todo)].GetFields();
foreach (var member in members)
{
Console.WriteLine($"{member.FieldNumber}: {member.Member.Name}");
}
However, I am not getting those inherited from the Base object:
❯ dotnet run
10: Title
20: Content
30: Category
The field metadata for protobuf-net is available from the RuntimeTypeModel API, for example:
var members = RuntimeTypeModel.Default[yourType].GetFields();
foreach (var member in members)
{
Console.WriteLine($"{member.FieldNumber}: {member.Member.Name}");
}
The .FieldNumber gives the protobuf field-number, and .Member gives the MemberInfo of the corresponding field or property. You may want to do some level of caching if the m => m.SetElementName( WHAT TO PUT HERE ) is evaluated lots of times for the same m, so you don't perform unnecessary work - but: before you do, just add some logging to the lambda first, and see how often it gets called: if it isn't too often, maybe don't worry about it.
Note that there is also a lookup on MetaType that allows query by MemberInfo:
var member = RuntimeTypeModel.Default[yourType][memberInfo];
Re the edit; in this region:
var members = RuntimeTypeModel.Default[typeof(T)].GetFields();
foreach (var member in members)
{
memberMap.SetElementName(member.FieldNumber.ToString());
}
I believe you're meant to identify the relevant field from memberMap - i.e. in this context you're only talking about one field at the time; I suspect what is happening is that for each member in turn you're changing the element name multiple times, leaving it at the last protobuf field defined.
Separately, there's a complication of inheritance; protobuf-net doesn't implement inheritance in a flat way - instead, the base type is also expected to be a [ProtoContract] and is meant to define a [ProtoInclude(...)] for each derived type; the field numbers are type-specific, meaning: both the base type and the derived type can legally have a field 1. If you need to describe inheritance, and you are determined to use protobuf-net's model, then you would need to handle this; for example, you could use the [ProtoInclude(...)] number as a prefix on each, so Base.Id is "1", and if we imagine that Todo has field 5 in the [ProtoInclude(...)], then Todo.Title could be "5.10".
Alternatively: if you're not actively using protobuf-net: maybe just use your own attribute for the numbers? or there's usually an inbuilt attribute that the serializer you've chosen would use directly.
Okay now! So after a some investigation I end up with this simple way to do it with Marc's help. In MongoDB instead of using attributes to decorate models and its properties, it is possible to use code within BsonClassMap. Within that class I add the foreach loop that Marc provided and the right parameters, we can now have numbers instead names.
On the Client side and Server side it is this same code:
//Base Model ClassMap
BsonClassMap.RegisterClassMap<Base>(cm =>
{
cm.AutoMap();
foreach (var member in RuntimeTypeModel.Default[typeof(Base)].GetFields())
{
cm.MapMember(typeof(Base).GetMember(member.Member.Name)[0])
.SetElementName(member.FieldNumber.ToString())
.SetOrder(member.FieldNumber);
}
});
//Todo Model ClassMap
BsonClassMap.RegisterClassMap<Todo>(cm =>
{
cm.AutoMap();
foreach (var member in RuntimeTypeModel.Default[typeof(Todo)].GetFields())
{
cm.MapMember(typeof(Todo).GetMember(member.Member.Name)[0])
.SetElementName(member.FieldNumber.ToString())
.SetOrder(member.FieldNumber);
}
});
it's a little ugly but you can rework it.
One thing to note is that MongoDB has the control over the Id. In the database anything that represent the object id become _id. Same thing when you insert a new document in the database a _t field is added if you use Discriminator (I am not sure if it's full related). Basically, every member beginning with a underscore is reserved. See the image below after running de code:
You can refer to the question above in the update section to see if this result represent the models with the given orders (it does).
Here is the code I use for insertion and queries:
// INSERT
var client = channel.CreateGrpcService<IBaseService<Todo>>();
var reply = await client.CreateOneAsync(
new Todo
{
Title = "Some Title"
}
);
// FIND BY ID
var todoId = new UniqueIdentification { Id = "613c110a073055f0d87a0e27"};
var res = await client.GetById(todoId);
// FIND ONE BY QUERY FILTER REQUEST
...
var filter = Builders<Todo>.Filter.Eq("10", "Some Title");
var filterString = filter.Render(documentSerializer, serializerRegistry);
...
The last one above it's a query with the number ("10") of the property Title. But it's possible in the same way to query with the property name, like so:
// FIND ONE BY QUERY FILTER REQUEST
...
var filter = Builders<Todo>.Filter.Eq(e => e.Title, "Some Title");
var filterString = filter.Render(documentSerializer, serializerRegistry);
...
What is great with this approach is that these BsonClassMap are called once on the Client or/and Server when they are initiated.
I just realize that this might not be a good idea because it is going to be painful to prevent collision between numbers. The order numbers in the code below is possible:
[BsonDiscriminator("Base", RootClass = true)]
[DataContract]
public abstract class Base
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[ProtoMember(1)]
public string Id { get; set; }
[BsonDateTimeOptions]
[ProtoMember(2)]
public DateTime CreatedDate { get; private set; } = DateTime.UtcNow;
[BsonDateTimeOptions]
[ProtoMember(3)]
public DateTime UpdatedDate { get; set; } = DateTime.UtcNow;
}
[ProtoContract]
public class Todo : Base
{
[ProtoMember(1)]
public string Title { get; set; }
[ProtoMember(2)]
public string Content { get; set; }
[ProtoMember(3)]
public string Category { get; set; }
}
but there is going to be three collisions if the foreach loop runs.
Yeah... :/
This is where Marc's second solution comes in, where you put a prefix... I am going to keep the name convention by default.
Cheers!

Automapper returns empty objects using ProjectTo<>

I'm new to Automapper, and trying to map a Entity Framework database object to a DTO.
My database object, OfficeLookup, contains Code and Description (among other properties I'm not concerned with).
My OfficeDto contains Id and Name properties.
The mapping:
private MapperConfiguration OfficeMapperConfiguration =>
new MapperConfiguration(
cfg =>
{
cfg.CreateMap<OfficeLookup, OfficeDto>()
.ForMember(dest => dest.Id, act => act.MapFrom(src => src.Code))
.ForMember(dest => dest.Name, act => act.MapFrom(src => src.Description));
});
And my code:
public IEnumerable<OfficeDto> GetOfficeDtos() => OfficeLookup.ProjectTo<OfficeDto>(OfficeMapperConfiguration); // returns an Ienumerable of empty OfficeDTOs
The strange thing is that I have another, more complex mapping for a different table which works fine. I don't understand why this map doesn't.
The expression generated by my mapping is
ObjectQuery<OfficeLookup>.MergeAs(MergeOption.AppendOnly).Select(dtoOfficeLookup => new OfficeDto())
which doesn't look right. The other mapping generates an expression which set the various properties correctly (with the exception of the Office value. I assume that once I can get this standalone mapping, I will be able to fix the nested mapping).
The problem should be in corresponding OfficeDto properties, and more specifically, the lack of property setter, in which case AutoMapper simply skips them from projection even though they have been mapped explicitly.
e.g. the issue is reproduced with the following class:
class OfficeDto
{
public int Id { get; }
public string Name { get; }
}
And adding property setters (even private) fixes it:
class OfficeDto
{
public int Id { get; private set; }
public string Name { get; set; }
}

Deep cloning an object with Automapper, can't exclude Id property on objects in a List<abstractBaseClass> property

I have the following classes:
public abstract class Question : IQuestion
{
[Key]
public int Id { get; set; }
// Some other base properties here
}
public class TextQuestion : Question
{
// Some class-specific properties here
}
And a class like this:
public class SomeCompositeClass
{
[Key]
public int Id { get; set; }
// Some properties go here ...
public virtual List<Question> Questions { get; set; }
}
I want to create a deep clone of the SomeCompositeClass, using Automapper (please don't suggest ICloneable), but without all the IDs, because I will be inserting it in the database, which I access with EntityFramework, repository pattern.
Naturally, I create a mapping:
Mapper.CreateMap<SomeCompositeClass, SomeCompositeClass>().ForMember(rec => rec.Id, opt => opt.Ignore())
and this works out great for the SomeCompositeClass.
But I have a problem doing the same thing for the Questions property! The problem comes from the base class in the List being abstract, not because the list itself is virtual, I have already ruled this out.
If I create a Mapper.CreateMap<Question, Question>() or Mapper.CreateMap<IQuestion, IQuestion>() mapping, the code throws an exception at runtime, complaining that it cannot create an instance of an abstract (Question) object.
I have tried Mapper.CreateMap<List<Question>, List<Question>>(), but this just gives me an empty Questions list at runtime.
I have tried creating question-specific mappings (TextQuestion to TextQuestion), but they don't kick in, because the objects in the Questions property are wrapped in EF's DynamicProxy classes.
What can I do, to exclude the Id from the inheritors of my abstract base Question class, during the Mapper.Map(...)?
I solved it the following way:
First, I updated to Automapper 4.1.1. Then:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Question, Question>()
.Include<TextBoxQuestion, TextBoxQuestion>()
// Supposedly inheritance mapping?
.ForMember(rec => rec.Id, opt => opt.Ignore());
cfg.CreateMap<TextBoxQuestion, TextBoxQuestion>()
// But either I don't understand inheritance mapping or it doesn't work, soI have to do that too
.ForMember(rec => rec.Id, opt => opt.Ignore());
cfg.CreateMap<SomeCompositeClass, SomeCompositeClass>()
.ForMember(rec => rec.Id, opt => opt.Ignore())
}
...
Mapper.Map(source, destination);
and it works...
So I think what I was mostly missing was the .Include part, which tells Automapper to look for the most derived class.

How to specify the shape of results with WebApi2, OData with $expand

I have a problem executing my AutoMapper mappings when using OData with specific $select or $expand values.
Using the WebApi Action:
public IQueryable<BookInRequestDto> Get(ODataQueryOptions<BookInRequest> query)
{
var results = query.ApplyTo(_context.BookInRequests) as IQueryable<BookInRequest>;
var mappedResults = Mapper.Map<IQueryable<BookInRequest>, IQueryable<BookInRequestDto>>(results);
return mappedResults;
}
When I query: api/Get, I get an appropriate response, but a Document's Properties is set to null response containing documents properties set to null.
When I query: api/Get?$expand=Documents/Properties, the response is an empty array.
As I understand, this is because Select/Expand changes the shape of the response, so it no longer matches an IQueryable of BookInRequest, and instead returns an IQueryable.
I'm happy to return that, except I need to be able to apply the AutoMapper Mappings. Is there anything that can be done to enforce the shape of the query results?
I have the following entities:
public class BookInRequest {
//...
public virtual ICollection<BookInDocument> Documents { get; set; }
}
public class BookInDocument {
public ICollection<BookInDocumentProperty> Properties { get; set; }
}
With Corresponding DTO's that are pretty much identical, except for the BookInDocumentDto:
public class BookInDocumentDto {
public dynamic Properties { get; set; }
}
My Mapping definition is as follows:
Mapper.CreateMap<BookInRequest, BookInRequestDto>();
Mapper.CreateMap<BookInDocument, BookInDocumentDto>()
.ForMember(x => x.Properties,
y => y.MapFrom(z =>
DynamicHelpers.PropertiesAsDynamic(z.Properties)));

Searching the Entity Framework domain model utilising Code First

Got a very difficult EntityFramework Code First question. I'll keep this as simple as possible.
Imagine we have n number of classes, lets start with 2 for now
public class Person
{
public string Name { get; set; }
}
public class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
}
Now then, what I want to do is be able to search the domain model with a single string, i.e. something like DbContext.Search( "Foo" ). The call would search both the person and address tables for a string match and would return a list populated with both Person and Address entities.
Have to say I am not entirely clear how to go about it but I am considering using DataAnnotations to do something like this
public class Person
{
**[Searchable]**
public string Name { get; set; }
}
public class Address
{
**[Searchable]**
public string AddressLine1 { get; set; }
**[Searchable]**
public string AddressLine2 { get; set; }
}
Am I on the right track?
Should I use the Fluent API instead?
Reflection?
Any and all thoughts massively appreciated.
the Find method searches only in the Primary Key column. If we don't make any column explicitly primary key column then find method will throw error. Generally EF convention takes propertyName+id as the primary key in the class. But if you want to search with Name then Make add [Key] to the property. it will become primary key and u will be able to find properties.
dbContext.Addresses.find("Foo");
Create a new object type onto which you'll project 2 types of search results:
public class Result
{
public string MainField { get; set; }
// you may have other properties in here.
}
Then find entities of each type that match your criteria, projecting them onto this type:
var personResults = DbContext.Persons
.Where(p => p.Name == "Foo")
.Select(p => new Result{MainField = p.Name});
// don't forget to map to any other properties you have in Result as well
var addressResults = DbContext.Adresses
.Where(a =>
a.AddressLine1 == "Foo" ||
a.AddressLine2 == "Foo"
).
.Select(a => new Result{MainField = a.AddressLine1 + ", " + a.AddressLine2 });
// again, don't forget to map to any other properties in Result
Then merge the lists:
var allResults = personResults.Union(addressResults).ToList();
...at which point you can sort the list however you like.
"Result" and "MainField", are rather generic; just using them because I am not thoroughly aware of your domain model.