I have a business need to dynamically select ONLY the properties of a given model that are specified, similar to an OData select clause. I am currently using Mapster's ProjectToType functionality to populate view models from EF Core entities.
Is there any way to tell Mapster to only select a given list of properties in the query that it generates? Or a way to take the full model mapping, and change mappings at runtime in an instance of TypeAdapterConfig to ignore properties that aren't in a given list of properties?
The end solution needs to be generic and work with navigation properties, because it will be applied to all of our entities in the database. We also used DynamicLinq in some cases, not sure if that can be used on top of Mapsters ProjectToType functionality.
Example:
Entities (Some properties omitted for length):
namespace DataAccess.Entities
{
public class Series
{
public Guid Id { get; set; }
public string Description { get; set; }
public long? StackRank { get; set; }
public string EntityId { get; set; }
// Other properties
}
public class Model
{
public Guid Id { get; set; }
public string Description { get; set; }
public string EntityId { get; set; }
public long? StackRank { get; set; }
public Guid SeriesId { get; set; }
public virtual Series Series { get; set; }
// Other properties
}
}
View Models (Some properties omitted for length):
namespace Models
{
public class Model
{
public Guid Id { get; set; }
public string Description { get; set; }
public string EntityId { get; set; }
public long? StackRank { get; set; }
public Guid SeriesId { get; set; }
public virtual Series Series { get; set; }
// Other properties
}
public class Series
{
public Guid Id { get; set; }
public string Description { get; set; }
public long? StackRank { get; set; }
public string EntityId { get; set; }
// Other properties
}
}
Given a rest call to get a list of all Model view models, with this list of properties to include:
var properties = new List<string> {
"Id",
"EntityId"
"Description",
"Series.Id",
"Series.Description",
"Series.EntityId"
}
The results would return some type of dictionary, dynamic, or anonymous object that contained ONLY these properties, and the other properties would not even be included in the final select of the SQL query that gets created.
In the end, I decided to use Arca Artem's suggestion, with a little twist. I used reflection to grab a list of all properties of the model and cache them. After that, I compared the cached properties vs the list of properties to include, and ignored the properties that weren't in both lists. Kinda like this:
var clonedConfig = mapsterInstance.Clone();
clonedConfig.ForType<TSource, TDestination>().Ignore(propertiesToIgnore);
var models = await query.ProjectToType<TDestination>(clonedConfig).ToListAsync();
Maybe not the most elegant solution, but it worked well enough for what I needed. I also set up our json serializer to ignore null values.
Related
Let's say I have a nested DTO, like this (in reality it is a lot more complex):
public class OrderDTO
{
public int Id { get; set; }
public List<ItemDTO> Item { get; set; }
}
public class ItemDTO
{
public int Id { get; set; }
}
that maps to:
public class Order
{
public int Id { get; set; }
public List<Item> Item { get; set; }
}
public class Item
{
public int Id { get; set; }
public Order Order { get; set; }
}
When mapping from the OrderDTO down to Order, I would like that the Order property from Item references its order, as to make something like var order = item.Order; possible.
Is there any way I can do that when mapping? Also, is it possible to configure that for all my nested mappings?
Using BeforeMap you can save the parent object in context.Items and then use that value in a resolver for the parent reference. If you respect a naming convention, you can use ForAllMaps to apply it wherever you need.
I have a very basic EF setup that is throwing an odd error when trying to populate a navigation property by using .Include. Here are the entity Models:
public class LineGroup
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public ICollection<LineGroupMember> LineGroupMembers { get; set; }
}
public class LineGroupMember
{
public int ID { get; set; }
public int Extension { get; set; }
public string Name { get; set; }
public int Permissions { get; set; }
public bool IsLoggedIn { get; set; }
public int LineGroupID { get; set; }
internal LineGroup LineGroup { get; set; }
}
I am using these through an injected DB context, and can query each just fine without using navigation properties. I can also query the LineGroups and include the LineGroupMembers property just fine, like so:
var LineGroups = _context.LineGroups.Include(l => l.LineGroupMembers).ToList();
This load all of the line groups into a list that has a correctly working "LineGroupMembers" collection for each Line Group. However, if I try
var lineGroupMembers = _context.LineGroupMembers.Include(m => m.LineGroup).ToList();
I get "NullReferenceException" with no helpful details. Any ideas why the navigation property will work one way and not the other? There are no null values in either database table...
Make your navigation property public
public LineGroup LineGroup { get; set; }
If it is internal it won't be picked up by default by EF. You could also add explicit fluent mapping to force EF to recognize it as well.
Using Entity Framework Code first I have a class that holds data for a drop-down list. The same class holds records that are sub-items for the items in the main list. Ultimately this will create a cascading set of drop-down lists.
I am trying to figure out how to make the navigation property for the class link back to itself. The issue class is the one that I am using to populate the drop-down list. The Complaint class also has a link to the Issues class but does not need a link back to the subcategory.
public class Issue
{
public Issue()
{
Complaints = new List<Complaint>();
SubIssues = new List<Issue>();
}
[Key]
public int IssueID { get; set; }
public string Name { get; set; }
public bool IsSubCategory { get; set; }
[ForeignKey("IssueID")]
public ICollection<Issue> SubIssues { get; set; }
public virtual ICollection<Complaint> Complaints { get; set; }
}
public class Complaint
{
public Complaint()
{
}
public int ComplaintID { get; set; }
public string Name {get; set;}
[ForeignKey("IssueID")]
public virtual Issue Issue { get; set; }
}
I did something similar, but actually did only have a parent reference in the children. Either way this should work.
public class Folder
{
[Key]
public int Id { get; set; }
// Some Property
public string Name { get; set; }
// They foreignkey for Many-side
public virtual Folder Parent { get; set; }
// The list for One-side (Not tested in my application)
public virtual ICollection<Folder> SubFolders { get; set; }
}
It is same as a regular one-to-many relation, just all the references are within same entity.
Consider this entity:
public class CondRule
{
public virtual decimal Id { get; set; }
public virtual string Name { get; set; }
public virtual CondRuleType RuleType { get; set; }
public virtual string Statement { get; set; }
}
and CondRuleType is:
public class CondRuleType
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
It is obvious that there is a one to one relation between CondRule and CondRuleType entities.
Also I have CondRuleDto:
public class CondRuleDto
{
public decimal Id { get; set; }
public string Name { get; set; }
public CondRuleType RuleType { get; set; }
}
I have mapped CondRule to CondRuleDto using AutoMapper:
Mapper.CreateMap<CondRule, CondRuleDto>();
When I call Session.Get to get CondRule by id and the map the result to CondRuleDto, AutoMapper does not resolve proxies (here RuleType).
Here is my code:
var condRule = Session.Get<CondRule>(id);
var condRuleDto = Mapper.Map<CondRuleDto>(condRule);
When I watch condRuleDto, RuleType property is a NHibernate proxy. I want AutoMapper to map RuleType proxy to a POCO. How to make this work?
PS: I have to mention that when I use query and use automapper's Project, it will result a list with no proxies (I know that Project make this happen. May be I need something like Project to use after Session.Get):
Session.Query<CondRule>().Project().To<CondRuleDto>().ToList()
Casts won't change the underlying object (i.e. your CondRuleType will be still a proxy even if you map its instance to another property of type CondRuleType).
It seems like you need to create a custom mapping where CondRule.RuleType is mapped creating a new instance of CondRuleType.
I have a class, "Search". See definition below:
public class Search
{
[Key]
public int SearchID { get; set; }
public int UserID { get; set; }
public SearchParameters SearchParameters { get; set; }
public ICollection<SearchProvider> SearchProviders { get; set; }
public User User;
}
SearchParameters is a class with value types, and a few sub-classes; as defined below:
public class SearchParameters
{
public List<string> SearchTerms { get; set; }
public int MaxRecords { get; set; }
public DistanceParameter Distance { get; set; }
public PriceRangeParameter PriceRange { get; set; }
}
The idea is that I do not want a separate SearchParameters table that has to link to the Search table because every property of the search is always one to one (Except for SearchTerms). Really, what I want EF to do is 'bring up' the child classes' properties so we end up with All the properties of SearchParameter in the SearchTable (and all the parameters of the DistanceParameter and PriceRangeParameter objects themselves). What annotations or other logic would I need for this to work? Thanks!
I think EF Complex Type mapping is what you need, see more here:
http://weblogs.asp.net/manavi/archive/2011/03/28/associations-in-ef-4-1-code-first-part-2-complex-types.aspx