Automapper with custom lazy loading objects - c#

I have a Person class which contains a property that lazy loads (custom made lazy loading) the person address data through accessing the Item property. I would want it to be mapped to a POCO class. How could it be done?
In addition, is it possible to be mapped only if it has data (checking the HasData property) and mapped as null if there isn’t data?.
These are the source classes:
public class SourcePerson
{
public string Name { get; set; }
public MyLazyLoadingObject<SourceAddress> Address;
}
public class SourceAddress
{
public string City { get; set; }
public string Country { get; set; }
}
This is the custom lazy loading class (simplified):
public class MyLazyLoadingObject<T>
{
private int? _id;
private T _object;
public T Item
{
get
{
if (!_object.IsReaded)
{
_object.Read();
}
return _object;
}
}
public bool HasData
{
get
{
return _id.HasValue;
}
}
// Other non-relevant properties and methods
}
These are the destination classes:
public class DestinationPerson
{
public string Name { get; set; }
public DestinationAddress Address;
}
public class DestinationAddress
{
public string City { get; set; }
public string Country { get; set; }
}

Couldn't find conventional way of setting up conversion from MyLazyLoadingObject<T> to T and then T to some TDestination without code repetition.
But custom IObjectMapper implementation with some manual expression building does the job.
Here is the class that builds the mapping expression:
public class MyLazyLoadingObjectMapper : IObjectMapper
{
public bool IsMatch(TypePair context)
{
return context.SourceType.IsGenericType && context.SourceType.GetGenericTypeDefinition() == typeof(MyLazyLoadingObject<>);
}
public Expression MapExpression(TypeMapRegistry typeMapRegistry, IConfigurationProvider configurationProvider, PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression)
{
var item = Expression.Property(sourceExpression, "Item");
Expression result = item;
if (item.Type != destExpression.Type)
{
var typeMap = configurationProvider.ResolveTypeMap(item.Type, destExpression.Type);
result = Expression.Invoke(typeMap.MapExpression, item, destExpression, contextExpression);
}
// source != null && source.HasData ? result : default(TDestination)
return Expression.Condition(
Expression.AndAlso(
Expression.NotEqual(sourceExpression, Expression.Constant(null)),
Expression.Property(sourceExpression, "HasData")
),
result,
Expression.Default(destExpression.Type)
);
}
}
All you need is to register it to the MapperRegistry:
AutoMapper.Mappers.MapperRegistry.Mappers.Add(new MyLazyLoadingObjectMapper());
and of course create the regular type maps (which I guess you already did):
cfg.CreateMap<SourceAddress, DestinationAddress>();
cfg.CreateMap<SourcePerson, DestinationPerson>();

I've achieved it this way:
cfg.CreateMap<SourcePerson, DestinationPerson>().ForMember(t => t.Address, o => o.MapFrom(s => (s.Address.HasData)? s.Address.Item : null));

Related

In C#, how do you count the number of properties contained within an expression body output function using reflection?

The following is a code block containing the ExpandClause expression body that I'd like to run the count on and I'd like to know how many properties are contained within the resultant object.
I'd prefer not to need to instantiate any of these objects if it can be avoided; reflection would be the preference.
My use case revolves around a unit test that would fail if more than say 5 properties were contained within an ExpandClause for a given concrete implementation of BaseRepository.
As an example, the below code should output 2 as the ExpandClause is using two properties, e.Name and e.Age.
public class Person
{
public int Age { get; set; }
public string EyeColour { get; set; }
public int Height { get; set; }
public string Name { get; set; }
}
public abstract class BaseRepository<TEntity>
{
protected abstract Expression<Func<TEntity, object>>? ExpandClause { get; }
}
public class PersonRepository : BaseRepository<Person>
{
protected override Expression<Func<Person, object>>? ExpandClause =>
e => new
{
e.Name,
e.Age
};
}
I've found a solution that works for me, thanks to #Jeremy's comment.
This class is what counts the members within the Expression:
public class ExpandClauseCounterExpressionVisitor : ExpressionVisitor
{
public int MemberCount { get; private set; }
public override Expression Visit(Expression node)
{
if (node.NodeType == ExpressionType.Lambda)
{
if (node is not LambdaExpression lambdaExpression)
{
throw new NullReferenceException(nameof(lambdaExpression));
}
if (lambdaExpression.Body is not NewExpression newExpression)
{
throw new NullReferenceException(nameof(newExpression));
}
MemberCount = newExpression.Members.Count;
}
return base.Visit(node) ?? throw new NullReferenceException("Cannot visit a null node");
}
}
This code is what actually calls the ExpressionVisitor and performs the count check:
var personRepository = new PersonRepository();
var expressionVisitor = new ExpandClauseCounterExpressionVisitor();
_ = expressionVisitor.Visit(personRepository.ExpandClause!);
if (expressionVisitor.MemberCount > 5)
{
// Do something
}

How to add a .ThenInclude for a nested object within a generic specification pattern in C#

I've implemented a generic spec pattern for my generic repo, but I don't know how I can add a .ThenInclude() to the code.
FYI - I have 3 entities (User->PracticedStyles->YogaStyles) and when I go to fetch my User I want to fetch all the YogaStyles he/she practices (ex. bikram, vinyasa, etc). But I can't get the YogaStyle entities, I can fetch all the PracticedStyle entities for the User because it's only one entity deep, but I can't figure out how to fetch/include the YogaStyle entity from each PracticedStyle.
I'm using a generic specification pattern with a generic repository pattern and I've created an intermediate table to hold all the styles, maybe this is wrong or I don't know how to use the generic spec pattern correctly?
public class User : IdentityUser<int>
{
public ICollection<PracticedStyle> PracticedStyles { get; set; }
}
public class PracticedStyle : BaseEntity
{
public int UserId { get; set; }
public User User { get; set; }
public int YogaStyleId { get; set; }
public YogaStyle YogaStyle { get; set; }
}
public class YogaStyle : BaseEntity
{
public string Name { get; set; } // strength, vinyasa, bikram, etc
}
Here is my controller and the methods the controller calls from
[HttpGet("{id}", Name = "GetMember")]
public async Task<IActionResult> GetMember(int id)
{
var spec = new MembersWithTypesSpecification(id);
var user = await _membersRepo.GetEntityWithSpec(spec);
if (user == null) return NotFound(new ApiResponse(404));
var userToReturn = _mapper.Map<MemberForDetailDto>(user);
return Ok(userToReturn);
}
public class MembersWithTypesSpecification : BaseSpecification<User>
{
public MembersWithTypesSpecification(int id)
: base(x => x.Id == id)
{
AddInclude(x => x.UserPhotos);
AddInclude(x => x.Experience);
AddInclude(x => x.Membership);
AddInclude(x => x.PracticedStyles);
// doesn't work - yogastyles is not a collection
// AddInclude(x => x.PracticedStyles.YogaStyles);
AddInclude(x => x.InstructedStyles);
}
}
Here is the 'AddInclude' from BaseSpecification
public class BaseSpecification<T> : ISpecification<T>
{
public BaseSpecification()
{
}
public BaseSpecification(Expression<Func<T, bool>> criteria)
{
Criteria = criteria;
}
public List<Expression<Func<T, object>>> Includes { get; } = new List<Expression<Func<T, object>>>();
protected void AddInclude(Expression<Func<T, object>> includeExpression)
{
Includes.Add(includeExpression);
}
}
Here is the getEntityWithSpec
public async Task<T> GetEntityWithSpec(ISpecification<T> spec)
{
return await ApplySpecification(spec).FirstOrDefaultAsync();
}
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
{
return SpecificationEvaluator<T>.GetQuery(_context.Set<T>().AsQueryable(), spec);
}
and spec evaluator
public class SpecificationEvaluator<TEntity> where TEntity : class // BaseEntity // when using BaseEntity, we constrain it to on base entities
{
public static IQueryable<TEntity> GetQuery(IQueryable<TEntity> inputQuery, ISpecification<TEntity> spec)
{
var query = inputQuery;
if (spec.Criteria != null)
{
query = query.Where(spec.Criteria); // e => e.YogaEventTypeId == id
}
if (spec.OrderBy != null)
{
query = query.OrderBy(spec.OrderBy);
}
if (spec.OrderByDescending != null)
{
query = query.OrderByDescending(spec.OrderByDescending);
}
if (spec.IsPagingEnabled)
{
query = query.Skip(spec.Skip).Take(spec.Take);
}
query = spec.Includes.Aggregate(query, (current, include) => current.Include(include)); // 'current' represents entity
return query;
}
}
I figured out what I needed.
Following this link
I needed to add
AddInclude($"{nameof(User.PracticedStyles)}.{nameof(PracticedStyle.YogaStyle)}");
and
query = specification.IncludeStrings.Aggregate(query,
(current, include) => current.Include(include));
and
public List<string> IncludeStrings { get; } = new List<string>();
protected virtual void AddInclude(string includeString)
{
IncludeStrings.Add(includeString);
}
and that allowed me to use .thenInclude(), but as a series of strings.
Here is a solution. Make your AddInclude methods return something like ISpecificationInclude:
public interface ISpecificationInclude<TFrom, TTo>
where TFrom : IEntity
where TTo : IEntity
// I know that you do not have a `IEntity` interface, but I advise you to
// add it to your infrastructure and implement it by all your entity classes.
{
ISpecificationInclude<TTo, TAnother> ThenInclude<TAnother>(Expression<Func<TTo, TAnother>> includeExpression);
ISpecificationInclude<TTo, TAnother> ThenInclude<TAnother>(Expression<Func<TTo, IEnumerable<TAnother>>> collectionIncludeExpression);
}
Implement this interface appropriately. The implementation should be a wrapper around a single "include" expression. You probably need two implementations: one for wrapping a collection include and one for a simple object include.
The Includes property of BaseSpecification class should be a collection of this interface.
In your SpecificationEvaluator, process your Includes, and all the ThenIncludes they may have, recursively.
I know that it's lots of code, but I'm afraid there is no other way :)

Pass property name as parameter

I have the following class
public class School
{
public List<Student> Students { get; set; }
public List<Teacher> Teachers { get; set; }
}
Now i have this method
public bool Evaluate(??)
{
var school = DbContext.Schools.FirstOrDefault();
return school.??.Any(/*some expresions*/)
}
I should be able to pass a value in ?? and use it so that i can use both
return school.Students.Any(/*some expresions*/)
return school.Teachers.Any(/*some expresions*/)
So how can i replace the question marks with Students or Teachers ?
Edit:
public class Student
{
public string FullName { get; set; }
public bool Registered { get; set; }
public bool Passed { get; set; }
}
public class Teacher
{
public string FullName { get; set; }
public bool CanEvaluate { get; set; }
public bool Validator { get; set; }
}
public class DynamicCheckTest
{
public bool MyExpression<T>(List<T> items, string name,
Expression<Func<T, bool>> expression)
{
return items.Any(x => expression.Compile()(x));
}
}
public static bool Check<T>(this List<T> items, Func<T, bool> compiledExp)
{
return items.Any(x => compiledExp(x));
}
Students.Check(x => x.Name == "Mike" && x.Registered); // example
Teachers.Check(x => x.Name == "Jack" && x.CanEvaluate);// example
Now i have to pass the school along which contains both Students and Teachers
But i don't know which one will be called in advance
You could use this method:
public bool Evaluate<T>(Func<School, List<T>> project, Func<T, bool> filter)
{
var school = DbContext.Schools.FirstOrDefault();
return project(school).Any(filter);
}
If we assume that the implementation of Student and Teacher are this:
public class Student
{
public string Name;
}
public class Teacher
{
public string Subject;
}
Then you could do this:
bool hasFred = Evaluate(school => school.Students, student => student.Name == "Fred Nerk");
bool teachArt = Evaluate(school => school.Teachers, teacher => teacher.Subject == "Art");
Addressing the "Pass property name as parameter" request, you could use reflection for that, but I don't think that's a good way to go. Instead, a Func<School, List<TElement>> could be used to select the desired List<> property to evaluate...
public bool Evaluate<TElement>(Func<School, List<TElement>> listSelector)
where TElement : Person
{
School school = DbContext.Schools.FirstOrDefault();
DateTime today = DateTime.Today;
return listSelector(school)
// For example, check if today is the birthday of anyone in the selected list
.Any(person => person.DateOfBirth.Month == today.Month && person.DateOfBirth.Day == today.Day);
}
As #Enigmativity points out, the type constraint is necessary in order to pass much of a meaningful condition to Any(), which also assumes/requires that Student and Teacher have common ancestry, like this...
public abstract class Person
{
public DateTime DateOfBirth
{
get;
}
}
public class Student : Person
{
}
public class Teacher : Person
{
}
You'd then use a lambda expression to specify the desired List<>...
bool isAnyStudentsBirthday = Evaluate(school => school.Students);
bool isAnyTeachersBirthday = Evaluate(school => school.Teachers);
This will work as long as the members you want Any() to consider are available in the constrained type (i.e. Person). If you wanted to filter using members specific to the Student or Teacher class, your best bet would be to use an approach like #Enigmativity's answer, where the filter itself is a parameter and receives the same derived type as the selected List<> stores.
Note that if you ever want to use Evaluate() with some other collection property of School that is not specifically List<>, or just knowing that all Any() needs is an IEnumerable<>, you could change the return type (last type parameter) of the Func<> to something less-restrictive...
Func<School, IList<TElement>>
Func<School, ICollection<TElement>>
Func<School, IEnumerable<TElement>>

Error when mapping from Entity to DTO or DTO to Entity using AutoMapper

I'm using EntityFramework as a DataLayer and DTO to transfer data between layer. I develop Windows Forms in N-Tier architecture and when I try to mapping from Entity to DTO in BLL:
public IEnumerable<CategoryDTO> GetCategoriesPaged(int skip, int take, string name)
{
var categories = unitOfWork.CategoryRepository.GetCategoriesPaged(skip, take, name);
var categoriesDTO = Mapper.Map<IEnumerable<Category>, List<CategoryDTO>>(categories);
return categoriesDTO;
}
I've got this error:
http://s810.photobucket.com/user/sky3913/media/AutoMapperError.png.html
The error said that I missing type map configuration or unsupported mapping. I have registered mapping using profile in this way at UI Layer:
[STAThread]
static void Main()
{
AutoMapperBusinessConfiguration.Configure();
AutoMapperWindowsConfiguration.Configure();
...
Application.Run(new frmMain());
}
and AutoMapper configuration is in BLL:
public class AutoMapperBusinessConfiguration
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<EntityToDTOProfile>();
cfg.AddProfile<DTOToEntityProfile>();
});
}
}
public class EntityToDTOProfile : Profile
{
public override string ProfileName
{
get { return "EntityToDTOMappings"; }
}
protected override void Configure()
{
Mapper.CreateMap<Category, CategoryDTO>();
}
}
public class DTOToEntityProfile : Profile
{
public override string ProfileName
{
get { return "DTOToEntityMappings"; }
}
protected override void Configure()
{
Mapper.CreateMap<CategoryDTO, Category>();
}
}
I've got the same error too when mapping from DTO to Entity.
category = Mapper.Map<Category>(categoryDTO);
How to solve this?
Its because you are using Mapper.Initialize multiple times. If you look at the source code it calls Mapper.Reset() which means only the last mapping defined will work. so instead simply remove the Initialize calls and replace with Mapper.AddProfile< >
Use AutoMapper.AssertConfigurationIsValid() after the Configure() calls. If anything fails it will throw an exception with a descriptive text. It should give you more info to debug further.
Mapping DTOs to Entities using AutoMapper and EntityFramework
here we have an Entity class Country and an CountryDTO
public class Country
{
public int CountryID { get; set; }
public string ContryName { get; set; }
public string CountryCode { get; set; }
}
CountryDto
public class CountryDTO
{
public int CountryID { get; set; }
public string ContryName { get; set; }
public string CountryCode { get; set; }
}
Create Object of CountryDTO
CountryDTO collection=new CountryDTO();
collection.CountryID =1;
collection.ContryName ="India";
collection.CountryCode ="in";
Country model = Convertor.Convert<Country, CountryDTO>(collection);
dbcontext.Countries.Add(model);
dbcontext.SaveChanges();
this will work fine for a new Country, the above code will map CountryDTO to Country Entity Object and add new entities to the dbcontext and save the changes.
using System.Reflection;
public static TOut Convert<TOut, TIn>(TIn fromRecord) where TOut : new()
{
var toRecord = new TOut();
PropertyInfo[] fromFields = null;
PropertyInfo[] toFields = null;
fromFields = typeof(TIn).GetProperties();
toFields = typeof(TOut).GetProperties();
foreach (var fromField in fromFields)
{
foreach (var toField in toFields)
{
if (fromField.Name == toField.Name)
{
toField.SetValue(toRecord, fromField.GetValue(fromRecord, null), null);
break;
}
}
}
return toRecord;
}
public static List<TOut> Convert<TOut, TIn>(List<TIn> fromRecordList) where TOut : new()
{
return fromRecordList.Count == 0 ? null : fromRecordList.Select(Convert<TOut, TIn>).ToList();
}
http://bhupendrasinghsaini.blogspot.in/2014/09/convert-enity-framwork-data-in-entity.html

Child tables in NHibernate

Is there any way in NHibernate that I can use the following Entities
public class Person
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Pet> Pets { get; set; }
}
public class Pet
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
And not have to create a "special" AddPet method on Person in order to have Child pets saved.
public void AddPet(Pet p)
{
p.Person = this;
Pets.Add(p);
}
_session.SaveOrUpdate(person);
Does not save the Pets because Pet has no Person reference.
If I update Pets to contain this reference.
public class Pet
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Person Person { get; set; }
}
On new pets I still have to set Person this seems like overkill to me and also risky as People can still call
person.Pets.Add(new Pet())
The only other option I can think of is a Custom list that sets the parent reference when adding child entities.
I modified your example just a bit (in line with many of the suggestions here):
public class Person
{
private IList<Pet> pets;
protected Person()
{}
public Person(string name)
{
Name = name;
pets = new List<Pet>();
}
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual IEnumerable<Pet> Pets
{
get { return pets; }
}
public virtual void AddPet(Pet pet)
{
pets.Add(pet);
}
}
public class Pet
{
protected Pet()
{}
public Pet(string name)
{
Name = name;
}
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name);
HasMany(x => x.Pets).Cascade.AllDeleteOrphan().Access.AsLowerCaseField();
}
}
public class PetMap : ClassMap<Pet>
{
public PetMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name);
}
}
The following test:
[Test]
public void CanSaveAndRetrievePetAttachedToPerson()
{
Person person = new Person("Joe");
person.AddPet(new Pet("Fido"));
Session.Save(person);
Person retrievedPerson = Session.Get<Person>(person.Id);
Assert.AreEqual("Fido", retrievedPerson.Pets.First().Name);
}
passes.
Note that this is using Fluent NHibernate for the mapping and the Session.
C# Reflection & Generics
Might be able to use this approach?
Wont work for children of children but its pretty good for Parent-Children.
protected static void SetChildReferences(E parent)
{
foreach (var prop in typeof(E).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!prop.CanRead) continue;
Type listType = null;
foreach (Type type in prop.PropertyType.GetInterfaces())
{
if (type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof(ICollection<>))
{
listType = type.GetGenericArguments()[0];
break;
}
}
List<PropertyInfo> propsToSet = new List<PropertyInfo>();
foreach (PropertyInfo childProp in
(listType ?? prop.PropertyType).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (childProp.PropertyType == typeof(E))
propsToSet.Add(childProp);
}
if (propsToSet.Count == 0) continue;
if (listType == null)
{
object child = prop.GetValue(parent, null);
if (child == null) continue;
UpdateProperties(propsToSet, child, parent);
}
else
{
ICollection collection = (ICollection)prop.GetValue(parent, null);
foreach (object child in collection)
{
if (child == null) continue;
UpdateProperties(propsToSet, child, parent);
}
}
}
}
protected static void UpdateProperties(List<PropertyInfo> properties, object target, object value)
{
foreach (PropertyInfo property in properties)
{
property.SetValue(target, value, null);
}
}
How do you know which pets does a person have in the database if you don't store the relation?
I would include the
public void AddPet(Pet p)
in the Person object and have a
IEnumerable<Pets> Pets { get; }
public property. The List property would be protected. This way person.Pets.Add(new Pet()) cannot happen and you're safe.
This is not a NHibernate issue, it is a domain model issue. NHibernate is not responsible to build up your references and back references of you domain.
I agree with gcores, make the list protected or private. Find a way to link your objects together in a consistent way. if it is not as simple as implementing a add method, consider a separate service class or factories. (If it is even more complicated, try something like Spring.Net.)
By the way, NHibernate probably stored the pets, but didn't have the information to write the correct foreign key. So it stored orphaned pets which never appeared in a pets list of any person.

Categories