C# AutoMapper with a child of itself - c#

Can you AutoMap on objects with children of themselves?
In this example:
public class Book
{
public int? BookKey { get; set; }
public Categories bookCategories { get; set; }
}
public class Categories
{
public int? CategoryKey { get; set; }
public List<Book> RecommendedBooks { get; set; }
}
Mapper.CreateMap<Common.BookList, Book>().IgnoreAllNonExisting();
Mapper.AssertConfigurationIsValid();
Mapper.CreateMap<Common.Categories, Categories>().IgnoreAllNonExisting();
Mapper.AssertConfigurationIsValid();
Swapping the last two maps around causes errors each time. Book first, means it doesn't understand categories, and categories first means it doesn't understand books.
AutoMapper.AutoMapperConfigurationException: The following property on Common.BookList / Common.Categories cannot be mapped.

You only need to call the configuration validation once. It might make sense to have it multiple times to make debugging easier (which gives you an exception closer to the location of the mapping code), but in this case, the maps are dependent on each other (Building a map of Book requires Automapper to know how to map bookCategories).
Change the code to the below, and it will work fine
Mapper.CreateMap<Common.BookList, Book>().IgnoreAllNonExisting();
Mapper.CreateMap<Common.Categories, Categories>().IgnoreAllNonExisting();
Mapper.AssertConfigurationIsValid();

Related

Merge properties from mapping table to single class

I have a website that is using EF Core 3.1 to access its data. The primary table it uses is [Story] Each user can store some metadata about each story [StoryUserMapping]. What I would like to do is when I read in a Story object, for EF to automatically load in the metadata (if it exists) for that story.
Classes:
public class Story
{
[Key]
public int StoryId { get; set; }
public long Words { get; set; }
...
}
public class StoryUserMapping
{
public string UserId { get; set; }
public int StoryId { get; set; }
public bool ToRead { get; set; }
public bool Read { get; set; }
public bool WontRead { get; set; }
public bool NotInterested { get; set; }
public byte Rating { get; set; }
}
public class User
{
[Key]
public string UserId { get; set; }
...
}
StoryUserMapping has composite key ([UserId], [StoryId]).
What I would like to see is:
public class Story
{
[Key]
public int StoryId { get; set; }
public bool ToRead { get; set; } //From user mapping table for currently logged in user
public bool Read { get; set; } //From user mapping table for currently logged in user
public bool WontRead { get; set; } //From user mapping table for currently logged in user
public bool NotInterested { get; set; } //From user mapping table for currently logged in user
public byte Rating { get; set; } //From user mapping table for currently logged in user
...
}
Is there a way to do this in EF Core? My current system is to load the StoryUserMapping object as a property of the Story object, then have Non-Mapped property accessors on the Story object that read into the StoryUserMapping object if it exists. This generally feels like something EF probably handles more elegantly.
Use Cases
Setup: I have 1 million stories, 1000 users, Worst-case scenario I have a StoryUserMapping for each: 1 billion records.
Use case 1: I want to see all of the stories that I (logged in user) have marked as "to read" with more than 100,000 words
Use case 2: I want to see all stories where I have NOT marked them NotInterested or WontRead
I am not concerned with querying multiple StoryUserMappings per story, e.g. I will not be asking the question: What stories have been marked as read by more than n users. I would rather not restrict against this if that changes in future, but if I need to that would be fine.
Create yourself an aggregate view model object that you can use to display the data in your view, similar to what you've ended up with under the Story entity at the moment:
public class UserStoryViewModel
{
public int StoryId { get; set; }
public bool ToRead { get; set; }
public bool Read { get; set; }
public bool WontRead { get; set; }
public bool NotInterested { get; set; }
public byte Rating { get; set; }
...
}
This view model is concerned only about aggregating the data to display in the view. This way, you don't need to skew your existing entities to fit how you would display the data elsewhere.
Your database entity models should be as close to "dumb" objects as possible (apart from navigation properties) - they look very sensible as they are the moment.
In this case, remove the unnecessary [NotMapped] properties from your existing Story that you'd added previously.
In your controller/service, you can then query your data as per your use cases you mentioned. Once you've got the results of the query, you can then map your result(s) to your aggregate view model to use in the view.
Here's an example for the use case of getting all Storys for the current user:
public class UserStoryService
{
private readonly YourDbContext _dbContext;
public UserStoryService(YourDbContext dbContext)
{
_dbContext = dbContext;
}
public Task<IEnumerable<UserStoryViewModel>> GetAllForUser(string currentUserId)
{
// at this point you're not executing any queries, you're just creating a query to execute later
var allUserStoriesForUser = _dbContext.StoryUserMappings
.Where(mapping => mapping.UserId == currentUserId)
.Select(mapping => new
{
story = _dbContext.Stories.Single(story => story.StoryId == mapping.StoryId),
mapping
})
.Select(x => new UserStoryViewModel
{
// use the projected properties from previous to map to your UserStoryViewModel aggregate
...
});
// calling .ToList()/.ToListAsync() will then execute the query and return the results
return allUserStoriesForUser.ToListAsync();
}
}
You can then create a similar method to get only the current user's Storys that aren't marked NotInterested or WontRead.
It's virtually the same as before, but with the filter in the Where to ensure you don't retrieve the ones that are NotInterested or WontRead:
public Task<IEnumerable<UserStoryViewModel>> GetForUserThatMightRead(string currentUserId)
{
var storiesUserMightRead = _dbContext.StoryUserMappings
.Where(mapping => mapping.UserId == currentUserId && !mapping.NotInterested && !mapping.WontRead)
.Select(mapping => new
{
story = _dbContext.Stories.Single(story => story.StoryId == mapping.StoryId),
mapping
})
.Select(x => new UserStoryViewModel
{
// use the projected properties from previous to map to your UserStoryViewModel aggregate
...
});
return storiesUserMightRead.ToListAsync();
}
Then all you will need to do is to update your View's #model to use your new aggregate UserStoryViewModel instead of your entity.
It's always good practice to keep a good level of separation between what is "domain" or database code/entities from what will be used in your view.
I would recommend on having a good read up on this and keep practicing so you can get into the right habits and thinking as you go forward.
NOTE:
Whilst the above suggestions should work absolutely fine (I haven't tested locally, so you may need to improvise/fix, but you get the general gist) - I would also recommend a couple of other things to supplement the approach above.
I would look at introducing a navigation property on the UserStoryMapping entity (unless you already have this in; can't tell from your question's code). This will eliminate the step from above where we're .Selecting into an anonymous object and adding to the query to get the Storys from the database, by the mapping's StoryId. You'd be able to reference the stories belonging to the mapping simply by it being a child navigation property.
Then, you should also be able to look into some kind of mapping library, rather than mapping each individual property yourself for every call. Something like AutoMapper will do the trick (I'm sure other mappers are available). You could set up the mappings to do all the heavy lifting between your database entities and view models. There's a nifty .ProjectTo<T>() which will project your queried results to the desired type using those mappings you've specified.

Multiple relations to the same object. Lists are empty

I have two tables in my database that are linked with 2x 1 to many relations to the same object.
Since we added the second DBLot2 to the database the list in DBLot is not filled with objects anymore.
Is there something we did wrong here?
public class DBNesting
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long DBNestingID { get; set; }
public DBLot DBLot { get; set; }
[ForeignKey("DBLot")]
public long DBLotID { get; set; }
public DBLot DBLot2 { get; set; }
[ForeignKey("DBLot2")]
public long? DBLot2ID { get; set; }
}
public class DBLot
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long DBLotID { get; set; }
public List<DBNesting> Nestingen { get; set; }
}
This is how we get the objects:
DatabaseContext dc = new DatabaseContext();
dc.DBNesting
.include("DBLot")
.include("DBLot2")
.where(...)
.ToList();
However the other side is not working:
dc.DBLot
.include("Nestingen")
.where(...)
.ToList()
I would expect that all the DBNesting where we used a DBLot in property
DBLot ore DBLot2 shoud be in Nestingen. But the collections are empty.
dc.DBLot
.include("Nestingen")
.where(...)
.ToList()
will not include the DBLot on the Nestingen only the direct object.
So it will have DBLot and a list of Nestingen but that list will not have the DBLot of each of the Nestingen in the list.
So basically you should be able to see... that you have recursion here, object has reference to object which reference itself.
dc.DBLot.include("Nestingen")
.include("Nestingen.DBLot")
.include("Nestingen.DBLot2")
.where(...)
.ToList()
may work, again will only now bring one level deeper, but if that all you need then awesome.
you could enable lazy loading... but also come with "responsibility" wouldn't recommend
ef 6 is not very efficient with include. also there is an extension which allows you to use typed version so include(x=>x.Nestingen), just take the string names out.
is the goal to have nested object relations.. nth levels. something like
Tree data structure in C#

Use JSON data set as seed for EntityFramework database

I'm trying to expose static objects using Microsoft.EntityFrameworkCore in a .netcoreapp 2.1.
The data I want to expose is in a .json file, and I can deserialize it without any issue into its corresponding c# classes.
Here's a sample of the structure : https://pastebin.com/SKCKsDJi
For the sake of clarity i suggest you read it using your favourite json reader
And here are the c# version of those objets :
public class FoodItem
{
public int Id { get; set; }
public string Name { get; set; }
public FoodType Type { get; set; }
public string Picture { get; set; }
public float Price { get; set; }
public string Currency { get; set; }
public IEnumerable<Ingredient> Ingredients { get; set; }
public bool IsVegetarian { get; set; }
}
public class FoodType
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Ingredient
{
public int Id { get; set; }
public string Name { get; set; }
}
Lets keep it simple though, there are ingredients, types and items, items are basically sandwiches, which are of a certain type and contain a list of ingredients. All 3 have id's to match them. This is where my problem lies, or so I think.
Everything works fine if I'm just using "Types" for example, in my dbcontext. As soon as I try to add either ingredients or items, or all 3 (which I need, but baby steps), I have the following error.
InvalidOperationException: The instance of entity type 'Ingredient' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
This is caused when :
public EatupController(EatupContext context)
{
_context = context;
var completeModel = JsonConvert.DeserializeObject<EatUpDataModel>(EatUpDataSet.Complete);
_context.Types.AddRange(completeModel.Types); //Works
_context.Ingredients.AddRange(completeModel.Ingredients); //Crashes here.
//If removed, all is fine but data is incomplete
//_context.Types.AddRange(completeModel.Types); //Unused
//_context.Items.AddRange(completeModel.Items); //Unused
_context.SaveChanges();
}
I don't understand why it's complaining about duplicate ID's, because they're all identical. Except, when I'm referencing the ingredient X in an item, obviously some items will use ingredients used by other items (many sandwiches have tomatoes). But surely that type of relation is allowed.
At first I had id's starting at 0 for all different types of objets, so ingredients ranged from 0 to about 100, items from 0 to 60, and types from 0 to 7. But since I had that error I edited all Id's and I still have the error, which is very confusing.
From what I read, this might also be due to using the context in different threads but this is not the case. If I remove the line that crashes, it stops crashing and I can see the data in the context correctly. In this case, only the types. If I add only the items or the ingredients in the context, it crashes for the same reason, just in another object (ingredient or item).
Where should I go from here? I don't even have a bad solution I could try to implement. My worst idea was to manually change the Id's (which is silly to me, it should work with the older ones), but even that failed.

Config Automapper to ignore type when it's an inner-inner property but not inner property

This one takes a little explaining. I have a set of types such that;
public class Child
{
public int ID { get; set;}
}
public class MayHaveChild
{
public Child Value { get; set; }
public int MayID { get; set; }
}
public class MustNotHaveChild { get; set; }
{
public List<MayHaveChild> MayValues { get; set; }
}
In the above scenario, I want any mapping of MayHaveChild to have the values for the Child object, except when I have mapped MustNotHaveChild. E.g.;
When I have
//...some code
MayHave obj = Mapper.Map<MayHaveChild>(childObj);
// I want to be able to access obj.Child.ID
But when I have
//...some code
MustNotHave obj = Mapper.Map<MustNotHaveChild>(notHaveObj);
// I want to be able to access obj.MayValues[0].MayID but
// *not* obj.MayValues[0].Value
I've been through the automapper documention on nesting, polymorphism, lists, etc and I can't find anything that quite matches what I want.
I could solve this by having a inheriting the MayHave class to a MustNotHave variant but this would involve changing quite a lot of existing code. Is there a way to configure Automapper in the manner I need?
I couldn't find a way to configure AutoMapper the way I wanted without going down the inheritance route - though this proved less problematic than I thought. I did something like the following;
public class NoChild : MayHaveChild
{
}
public class MustNotHaveChild { get; set; }
{
// \/--datatype change here
public List<NoChild> MayValues { get; set; }
}
Then, later in the AutoMapper config;
Mapper.CreateMap<MayHave, NoChild>()
.ForMember(c => c.Child, opt => opt.Ignore());

ValueInjector not mapping property

I'm having trouble trying to get ValueInjector to map my objects correctly. This is the code I am using for the mapping:
public IEnumerable<CategoryDTO> FindCategories(IList<object[]> criteria)
{
IEnumerable<Category> categories = _categoryRepo.Find(criteria);
IEnumerable<CategoryDTO> categoriesDto = Mapper.Map<IEnumerable<Category>, IEnumerable<CategoryDTO>>(categories);
return categoriesDto;
}
the variable categories contains a property:
IEnumerable<Standard> Standards
This property contains two Standard objects in the instance I'm calling on. The problem is when I map from my Category to my CategoryDTO. CategoryDTO is defined as this:
public class CategoryDTO : AuditableDTO
{
public Guid CategoryId { get; set; }
public string Name { get; set; }
public string MachineName { get; set; }
public string Description { get; set; }
public IEnumerable<StandardDTO> Standards { get; set; }
}
After the mapping statement is run, and I investigate the contents of categoriesDto.Standards, I can see that it is null. I would have expected my Standards to have mapped, but I'm sure I'm missing something with ValueInjector. Probably something along the lines of telling it how to map Standard to StandardDTO. Any thoughts?
EDIT: I need to clarify, I'm using this http://valueinjecter.codeplex.com/wikipage?title=Automapper%20Simulation&referringTitle=Home
EDIT 2: Digging deeper, I can see that my Iesi.Collections.HashedSet is causing the issue. Categorys' Standards property are typed as Iesi.Collections.ISet, this is turned into the HashedSet. So I guess my real question is how do I check the property for that type and how can I map?
My guess would be that the Mapper.Map doesn't know to map one level deeper than the IEnumerable. Have you tried looping though the collection, mapping it at the Category, CategoryDTO level vs the IEnumerable level?

Categories