Automapper: setting AllowNullCollections on the profile - c#

I'm using Automapper to map a class with a null collection to a destination with the same collection. I need the destination collection to also be null.
There is a property on the Profile class called AllowNullCollections. It is not affecting the mapping.
If I set cfg.AllowNullCollections to True the mapping does leave the destination collection as null (as I want).
I can't set the AllowNullCollections to True for all mappings in my system, it must only apply to my profile.
using System.Collections.Generic;
using AutoMapper;
using NUnit.Framework;
using Assert = NUnit.Framework.Assert;
namespace Radfords.FreshCool.Web.Tests
{
[TestFixture]
[Category("UnitTest")]
class AutomapperTests
{
private IMapper _mapper;
// this says that AllowNullCollections does work at the profile level, in May.
//https://github.com/AutoMapper/AutoMapper/issues/1264
[SetUp]
public void SetUp()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<TestMappingProfile>();
// I want the profile to set the configuration, if I set this here the test passes
//cfg.AllowNullCollections = true;
});
_mapper = config.CreateMapper();
}
[Test]
[Category("UnitTest")]
public void MapCollectionsTest_MustBeNull()
{
var actual = _mapper.Map<Destination>(new Source());
Assert.IsNull(actual.Ints, "Ints must be null.");
}
}
internal class TestMappingProfile : Profile
{
public TestMappingProfile()
{
AllowNullCollections = true;
CreateMap<Source, Destination>();
}
}
internal class Source
{
public IEnumerable<int> Ints { get; set; }
}
internal class Destination
{
public IEnumerable<int> Ints { get; set; }
}
}

Will Ray has submitted an issue on github. The current state is that you cannot set AllowNullCollections at the profit level, you must set it at the config level.

Can you replace TestMappingProfile Ctor with below and it should work :
public TestMappingProfile()
{
CreateMap<Source, Destination>().ForMember(dest => dest.Ints, opt => opt.Condition(src => (src.Ints != null)));
}

Related

Lazy Loading for Owned Types

I am moving my first steps towards Domain Driven Design using Entity Framework Core. I have a User entity that, in a simplified version, has only Id and ProfilePhoto. However, I want to store profile photos in a different table, that is why I created an Owned Type containing the profile photo and configured in this way:
User:
public class User
{
private int id;
public int Id => this.id;
//private UserProfilePhoto userProfilePhoto;
public virtual UserProfilePhoto UserProfilePhoto { get; set; }
private User()
{
}
public static User Create(byte[] profilePhoto)
{
var user = new User();
user.UserProfilePhoto = new UserProfilePhoto(profilePhoto);
return user;
}
public void SetProfilePhoto(byte[] profilePhoto)
{
this.UserProfilePhoto = new UserProfilePhoto(profilePhoto);
}
}
UserProfilePhoto:
public class UserProfilePhoto
{
public byte[] ProfilePhoto { get; private set; }
public UserProfilePhoto(byte[] profilePhoto)
{
this.ProfilePhoto = profilePhoto;
}
}
DbContext configuration:
public class ModelContext : DbContext
{
public ModelContext(DbContextOptions<ModelContext> options) : base(options)
{
}
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
OnUserModelCreating(modelBuilder);
}
protected void OnUserModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasKey(u => u.Id);
modelBuilder.Entity<User>()
.Property(u => u.Id)
.HasField("id");
modelBuilder.Entity<User>()
.OwnsOne(u => u.UserProfilePhoto, builder =>
{
builder.ToTable("UserProfilePhoto");
builder.Property(u => u.ProfilePhoto)
.IsRequired();
});
}
}
I chose to use an Owned type because I want the profile photo to be accessible only from the user entity. with a one-to-one mapping, I could still access the UserProfilePhoto table using context.Set<UserProfilePhoto>() for example and, for what I read about DDD aggregates, this could mean skipping User business logic.
So, I migrated and the database model is just like I expected it to be: the UserProfilePhoto table with a primary and foreign key to User.Id.
Obviously in my queries I do not want to load the entire User entity every time, so I enabled Lazy Loading, unsuccessfully. This is the code I tried in a unit test:
protected ModelContext GetModelContext(DbContextOptionsBuilder<ModelContext> builder)
{
builder
.UseLoggerFactory(loggerFactory)
.UseLazyLoadingProxies()
.EnableDetailedErrors();
var ctx = new ModelContext(builder.Options);
ctx.Database.EnsureCreated();
return ctx;
}
[TestMethod]
public async Task TestMethod1()
{
var builder = new DbContextOptionsBuilder<ModelContext>()
.UseSqlServer(...);
var ctx = this.GetModelContext(builder);
var user = User.Create(new byte[] { });
try
{
await ctx.Users.AddAsync(user);
await ctx.SaveChangesAsync();
var users = ctx.Users;
foreach (var u in users)
{
Console.WriteLine(u.Id);
}
}
finally
{
ctx.Users.Remove(user);
await ctx.SaveChangesAsync();
ctx.Database.EnsureDeleted();
}
}
And here the SQL generated:
SELECT [u].[Id], [u0].[UserId], [u0].[ProfilePhoto]
FROM [Users] AS [u]
LEFT JOIN [UserProfilePhoto] AS [u0] ON [u].[Id] = [u0].[UserId]
I do not exactly know if it works, but injecting an ILazyLoader is not an solution for me, on the other hand, it feels like dirtying the model.
My doubt is that Owned types do not bind to the principal entity through actual navigation properties, so creating proxy for them is not supported.
What is wrong with my approach? Is it DDD? And if so, how can I lazily load owned entities?
I found an issue on Github related to this, although it does not answer my question.
Edit
my goal is to prevent the access to the UserProfilePhoto table from EF api (See comments). If I managed to do this, then protecting my UserProfilePhoto class and encapsulating it in the User class would be easy, something like this:
User
...
protected virtual UserProfilePhoto UserProfilePhoto { get; set; }
public void SetProfilePhoto(byte[] profilePhoto)
{
this.UserProfilePhoto.SetProfilePhoto(profilePhoto);
}
public byte[] GetProfilePhoto()
{
return this.UserProfilePhoto.ProfilePhoto;
}
...
I tried this code with a one-to-one mapping and works perfectly, even with lazy loading. How could I do this with only Owned Types? are there other ways?
EF Core loads owned types automatically when the owner gets loaded (from Owned Entity Types: Querying owned types)
When querying the owner the owned types will be included by default. It is not necessary to use the Include method, even if the owned types are stored in a separate table.
Therefore using owned types does not fulfill your requirement of being loaded only on demand.
(You can tinker with Metadata.PrincipalToDependent.SetIsEagerLoaded(false) etc., but this is very much unsupported, unlikely to work in all cases and could break any time.)
Options without using owned types (in order of recommendation)
Override DbContext.Set<>(), DbContext.Find() etc. and throw if called inappropriately
Implement a traditional custom Unit-of-Work and Repository pattern, that gives you full control over the API exposed (trades flexibility for control)
Add an expression visitor early to the query pipeline (register IQueryTranslationPreprocessorFactory and derive from RelationalQueryTranslationPreprocessorFactory), that throws if a DbSet<UserProfilePhoto> is used anywhere in the query
Provide your own IDbSetSource (and InternalDbSet) implementation (both internal) and throw if called inappropriately
Overriding DbContext methods
Generally, just overriding DbContext.Set<>(), DbContext.Find() etc. should be the simplest solution. You could decorate the types that you don't want to be queried directly with a custom attribute and then simply just check, that TEntity etc. has not have been decorated with this custom attribute.
For easier maintainability, all the overridden methods can be moved to a base class, that can also perform some runtime check to ensure, that all methods in question have been overridden (of course those checks could also be done by a unit test).
Here is a sample demonstrating this approach:
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
[AttributeUsage(AttributeTargets.Class)]
public sealed class DontRootQueryMeAttribute : Attribute
{
}
public class User
{
public int Id { get; private set; }
public virtual UserProfilePhoto UserProfilePhoto { get; set; }
public static User Create(byte[] profilePhoto)
{
var user = new User
{
UserProfilePhoto = new UserProfilePhoto(profilePhoto)
};
return user;
}
}
[DontRootQueryMeAttribute]
public class UserProfilePhoto
{
public int Id { get; set; }
public byte[] ProfilePhoto { get; private set; }
public UserProfilePhoto(byte[] profilePhoto)
{
ProfilePhoto = profilePhoto;
}
}
public abstract class ModelContextBase : DbContext
{
private static readonly string[] OverriddenMethodNames =
{
nameof(DbContext.Set),
nameof(DbContext.Query),
nameof(DbContext.Find),
nameof(DbContext.FindAsync),
};
static ModelContextBase()
{
var type = typeof(ModelContextBase);
var overriddenMethods = type
.GetRuntimeMethods()
.Where(
m => m.IsPublic &&
!m.IsStatic &&
OverriddenMethodNames.Contains(m.Name) &&
m.GetRuntimeBaseDefinition() != null)
.Select(m => m.GetRuntimeBaseDefinition())
.ToArray();
var missingOverrides = type.BaseType
.GetRuntimeMethods()
.Where(
m => m.IsPublic &&
!m.IsStatic &&
OverriddenMethodNames.Contains(m.Name) &&
!overriddenMethods.Contains(m))
.ToArray();
if (missingOverrides.Length > 0)
{
throw new InvalidOperationException(
$"The '{nameof(ModelContextBase)}' class is missing overrides for {string.Join(", ", missingOverrides.Select(m => m.Name))}.");
}
}
private void EnsureRootQueryAllowed<TEntity>()
=> EnsureRootQueryAllowed(typeof(TEntity));
private void EnsureRootQueryAllowed(Type type)
{
var rootQueriesAllowed = type.GetCustomAttribute(typeof(DontRootQueryMeAttribute)) == null;
if (!rootQueriesAllowed)
throw new InvalidOperationException($"Directly querying for '{type.Name}' is prohibited.");
}
public override DbSet<TEntity> Set<TEntity>()
{
EnsureRootQueryAllowed<TEntity>();
return base.Set<TEntity>();
}
public override DbQuery<TQuery> Query<TQuery>()
{
EnsureRootQueryAllowed<TQuery>();
return base.Query<TQuery>();
}
public override object Find(Type entityType, params object[] keyValues)
{
EnsureRootQueryAllowed(entityType);
return base.Find(entityType, keyValues);
}
public override ValueTask<object> FindAsync(Type entityType, params object[] keyValues)
{
EnsureRootQueryAllowed(entityType);
return base.FindAsync(entityType, keyValues);
}
public override ValueTask<object> FindAsync(Type entityType, object[] keyValues, CancellationToken cancellationToken)
{
EnsureRootQueryAllowed(entityType);
return base.FindAsync(entityType, keyValues, cancellationToken);
}
public override TEntity Find<TEntity>(params object[] keyValues)
{
EnsureRootQueryAllowed<TEntity>();
return base.Find<TEntity>(keyValues);
}
public override ValueTask<TEntity> FindAsync<TEntity>(params object[] keyValues)
{
EnsureRootQueryAllowed<TEntity>();
return base.FindAsync<TEntity>(keyValues);
}
public override ValueTask<TEntity> FindAsync<TEntity>(object[] keyValues, CancellationToken cancellationToken)
{
EnsureRootQueryAllowed<TEntity>();
return base.FindAsync<TEntity>(keyValues, cancellationToken);
}
// Add other overrides as needed...
}
public class ModelContext : ModelContextBase
{
public DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
#"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63887500_01")
.UseLoggerFactory(LoggerFactory.Create(b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
OnUserModelCreating(modelBuilder);
}
protected void OnUserModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>(
entity =>
{
entity.HasOne(e => e.UserProfilePhoto)
.WithOne()
.HasForeignKey<UserProfilePhoto>(e => e.Id);
});
}
}
internal static class Program
{
private static void Main()
{
var accessingSetThrows = false;
using (var ctx = new ModelContext())
{
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
var user = User.Create(new byte[] { });
ctx.Users.Add(user);
ctx.SaveChanges();
// Make sure, that UserProfilePhoto cannot be queried directly.
try
{
ctx.Set<UserProfilePhoto>()
.ToList();
}
catch (InvalidOperationException)
{
accessingSetThrows = true;
}
Debug.Assert(accessingSetThrows);
}
// No eager loading by default with owned type here.
using (var ctx = new ModelContext())
{
var users = ctx.Users.ToList();
Debug.Assert(users.Count == 1);
Debug.Assert(users[0].UserProfilePhoto == null);
}
// Explicitly load profile photo.
using (var ctx = new ModelContext())
{
var users = ctx.Users.ToList();
ctx.Entry(users[0]).Reference(u => u.UserProfilePhoto).Load();
Debug.Assert(users.Count == 1);
Debug.Assert(users[0].UserProfilePhoto != null);
}
}
}
}
Providing an IQueryTranslationPreprocessorFactory implementation
An expression visitor can be used to solve the issue by using an IQueryTranslationPreprocessorFactory implementation to search the query for a specific expression, that is only added when the new InternalQuery() extension method is called and throwing, if it is missing and a non-root entity is being queried. In practice, this should be good enough to make sure, that nobody in the team queries non-root objects by accident.
(You could also add an internal class instance as a constant parameter to the method call expression, that is then evaluated later in the expression visitor to ensure, that the caller really had internal access to the InternalQuery() methods. But this is just icing on the cake and unnecessary in practice, since developers could use reflection to bypass any access restrictions anyway. So I wouldn't bother to implement this.)
Here it the implementation (using a custom interface instead of a custom attribute to mark entities that should not be queried directly):
using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
#region Models
public class User
{
public int Id { get; private set; }
public virtual UserProfilePhoto UserProfilePhoto { get; set; }
public static User Create(byte[] profilePhoto)
{
var user = new User
{
UserProfilePhoto = new UserProfilePhoto(profilePhoto)
};
return user;
}
}
public class UserProfilePhoto : INonRootQueryable
{
public int Id { get; set; }
public byte[] ProfilePhoto { get; private set; }
public UserProfilePhoto(byte[] profilePhoto)
{
ProfilePhoto = profilePhoto;
}
}
#endregion
#region Custom implementations
public interface INonRootQueryable
{
}
public class CustomQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory
{
private readonly QueryTranslationPreprocessorDependencies _dependencies;
private readonly RelationalQueryTranslationPreprocessorDependencies _relationalDependencies;
public CustomQueryTranslationPreprocessorFactory(
QueryTranslationPreprocessorDependencies dependencies,
RelationalQueryTranslationPreprocessorDependencies relationalDependencies)
{
_dependencies = dependencies;
_relationalDependencies = relationalDependencies;
}
public virtual QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext)
=> new CustomQueryTranslationPreprocessor(
_dependencies,
_relationalDependencies,
queryCompilationContext);
}
public class CustomQueryTranslationPreprocessor : RelationalQueryTranslationPreprocessor
{
public CustomQueryTranslationPreprocessor(
QueryTranslationPreprocessorDependencies dependencies,
RelationalQueryTranslationPreprocessorDependencies relationalDependencies,
QueryCompilationContext queryCompilationContext)
: base(dependencies, relationalDependencies, queryCompilationContext)
{
}
public override Expression Process(Expression query)
{
query = new ThrowOnNoneRootQueryableViolationExpressionVisitor().Visit(query);
return base.Process(query);
}
}
public class ThrowOnNoneRootQueryableViolationExpressionVisitor : ExpressionVisitor
{
private bool _isInternalQuery;
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.GetGenericMethodDefinition() == CustomQueryableExtensions.InternalQueryMethodInfo)
{
_isInternalQuery = true;
return node.Arguments[0];
}
return base.VisitMethodCall(node);
}
protected override Expression VisitConstant(ConstantExpression node)
{
var expression = base.VisitConstant(node);
// Throws if SomeEntity in a DbSet<SomeEntity> implements INonRootQueryable and the query was not chained
// to the `InternalQuery()` extension method.
return !_isInternalQuery &&
node.Type.IsGenericType &&
node.Type.GetGenericTypeDefinition() == typeof(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable<>) &&
node.Type.GenericTypeArguments.Length == 1 &&
typeof(INonRootQueryable).IsAssignableFrom(node.Type.GenericTypeArguments[0])
? throw new InvalidOperationException($"Directly querying for '{node.Type.Name}' is prohibited.")
: expression;
}
}
internal static class CustomQueryableExtensions
{
internal static readonly MethodInfo InternalQueryMethodInfo
= typeof(CustomQueryableExtensions)
.GetTypeInfo()
.GetDeclaredMethods(nameof(InternalQuery))
.Single(m => m.GetParameters().Length == 1 &&
m.GetParameters()[0].ParameterType.Namespace == $"{nameof(System)}.{nameof(System.Linq)}" &&
m.GetParameters()[0].ParameterType.Name.StartsWith(nameof(IQueryable)) &&
m.GetParameters()[0].ParameterType.GenericTypeArguments.Length == 1);
internal static IQueryable<TSource> InternalQuery<TSource>(this IQueryable<TSource> source)
=> source.Provider.CreateQuery<TSource>(
Expression.Call(
null,
InternalQueryMethodInfo.MakeGenericMethod(typeof(TSource)),
source.Expression));
internal static IQueryable<TProperty> InternalQuery<TEntity, TProperty>(this ReferenceEntry<TEntity, TProperty> source)
where TEntity : class
where TProperty : class
=> source.Query()
.InternalQuery();
}
#endregion
public class ModelContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Register the custom type IQueryTranslationPreprocessorFactory.
// Since this is a console program, we need to create our own ServiceCollection
// for this.
// In an ASP.NET Core application, the AddSingleton call can just be added to
// the general service configuration method.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkSqlServer()
.AddSingleton<IQueryTranslationPreprocessorFactory, CustomQueryTranslationPreprocessorFactory>()
.AddScoped(
s => LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.BuildServiceProvider();
optionsBuilder
.UseInternalServiceProvider(serviceProvider) // <-- use our ServiceProvider
.UseSqlServer(#"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63887500_05")
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
OnUserModelCreating(modelBuilder);
}
protected void OnUserModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>(
entity =>
{
entity.HasOne(e => e.UserProfilePhoto)
.WithOne()
.HasForeignKey<UserProfilePhoto>(e => e.Id);
});
}
}
internal static class Program
{
private static void Main()
{
var accessingSetThrows = false;
using (var ctx = new ModelContext())
{
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
var user = User.Create(new byte[] { });
ctx.Users.Add(user);
ctx.SaveChanges();
}
// Make sure, that UserProfilePhoto cannot be queried directly by default.
using (var ctx = new ModelContext())
{
try
{
ctx.Set<UserProfilePhoto>()
.ToList();
}
catch (InvalidOperationException)
{
accessingSetThrows = true;
}
Debug.Assert(accessingSetThrows);
}
// Make sure, that UserProfilePhoto can be queried directly, when using the `InternalQuery()` extension
// method.
using (var ctx = new ModelContext())
{
var userProfilePhotos = ctx.Set<UserProfilePhoto>()
.InternalQuery()
.ToList();
Debug.Assert(userProfilePhotos.Count == 1);
}
// No eager loading of referenced types by default.
using (var ctx = new ModelContext())
{
var users = ctx.Users.ToList();
Debug.Assert(users.Count == 1);
Debug.Assert(users[0].UserProfilePhoto == null);
}
// Eager loading of referenced types is allowed, when using the `InternalQuery()` extension method.
using (var ctx = new ModelContext())
{
var users = ctx.Users
.Include(u => u.UserProfilePhoto)
.InternalQuery()
.ToList();
Debug.Assert(users.Count == 1);
Debug.Assert(users[0].UserProfilePhoto != null);
}
// Explicitly load profile photo, when using the `InternalQuery()` extension method.
using (var ctx = new ModelContext())
{
var users = ctx.Users.ToList();
ctx.Entry(users[0])
.Reference(u => u.UserProfilePhoto)
.InternalQuery()
.Load();
Debug.Assert(users.Count == 1);
Debug.Assert(users[0].UserProfilePhoto != null);
}
}
}
}
Should bytes really be part of the domain? Do you actually run any business logic on those bytes in the user profile context? Is there really a use case where you'd want to access the bytes from within the User AR?
If not then perhaps it makes more sense decoupling the bytes storage from the photo's metadata and introduce a ProfilePhoto VO with a storageUrl/storageId property to locate the bytes.
Don't forget that your domain model should be designed for commands, not queries & the presentation layer.
Granted, now you can't easily have ACID properties when storing the bytes & the AR's data in the DB, but it's usually easy to cope with that with a cleanup process.
If you don't need profile photo's metadata in User to enforce business rules then you may also consider making ProfilePhoto it's own AR.
Finally, I think trying to prevent ORM misuse is unnecessary. The ORM should be seen as a low-level API which shouldn't ever be used directly to change AR states. I think it's safe to assume developers will have enough rigour to respect that rule just like they should respect the overall system's architecture. If they don't you have bigger problems. If it was as easy as adding a private modifier to a member then sure, but it seems to be needing a lot of efforts so I'd just go the pragmatic way...
I found a temporary solution:
modelBuilder.Entity<User>()
.OwnsOne(u => u.UserProfilePhoto, builder =>
{
builder.Metadata.IsOwnership = false;
builder.Metadata.IsRequired = false;
builder.Metadata.PrincipalToDependent.SetIsEagerLoaded(false);
builder.ToTable("UserProfilePhoto");
builder.Property(u => u.ProfilePhoto)
.IsRequired();
});
I do not like it and I guess EF allows you to configure that in other, more clear, ways. I am not accepting this answer, hoping someone else could point me in the right direction.
EDIT: proxy works this way but when a User is deleted, the association with the UserProfilePhoto is severed:
The association between entities 'User' and 'UserProfilePhoto' with
the key value '{UserId: 1}' has been severed but the relationship is
either marked as 'Required' or is implicitly required because the
foreign key is not nullable. If the dependent/child entity should be
deleted when a required relationship is severed, then setup the
relationship to use cascade deletes.
I even tried to specify through metadata the DeleteBehaviour.Cascade option but it probably breaks an internal constraint.
Moreover, it is now accessible via DbContext.Set<UserProfilephoto>(), which is not what I want.

Unit Testing a Class With A Private Constructor

I am trying to test a class that only has a private constructor. This is for a course registration system. The courses do not get create via our application, therefore we intentionally have no public constructor. Instead we use EF to get the courses that are already in the database, and register students to them.
I am trying to test the register method of the Course class, however I have no way of creating an instance. I could use
course = (Course)Activator.CreateInstance(typeof(Course), true);, but then I don't have a way to setup the necessary properties since those are private.
What is the recommended approach for unit testing without a constructor?
This is a slimmed down version of the code.
public class Course
{
private Course()
{
}
public int Id { get; private set; }
public string Name { get; private set; }
public bool Open { get; private set; }
public virtual ICollection<Student> Students { get; private set; }
public void Register(string studentName)
{
if (Open)
{
var student = new Student(studentName);
Students.Add(student);
}
}
}
// Usage //
using (var db = new SchoolContext())
{
var course = db.Courses.Include(x => x.Students).Where(x => x.Name == courseName).First();
course.Register(studentName);
db.SaveChanges();
}
// Unit Test //
[Fact]
public void CanRegisterStudentForOpenClass(){
// HERE I HAVE NO WAY TO CHANGE THE OPEN VARIABLE
var course = (Course)Activator.CreateInstance(typeof(Course), true);
course.Register("Bob");
}
Yes you can using reflexion. your code is neraly there;
you can get properties and fields of the types with typeof(Course).GetProperty("PropertyName") then you can use SetValue to set the desired value, and pass as parameter first the instance to modify then the value.
in your case true;
note: in your example you will need to add the Collection of students too, if your Open is true.
Here there is a working example:
[Fact]
public void CanRegisterStudentForOpenClass()
{
var course = (Course)Activator.CreateInstance(typeof(Course), true);
typeof(Course).GetProperty("Open").SetValue(course, true, null);
ICollection<Student> students = new List<Student>();
typeof(Course).GetProperty("Students").SetValue(course, students, null);
course.Register("Bob");
Assert.Single(course.Students);
}
If you would rather not use reflection, then I recommend you use internal classes (instead of private) and using the InternalsVisibleToAttribute on your implementation assembly.
You can find more about the attribute here. Here's a quick guide on how you can use it!
Step 1. Add this attribute to your assembly that wants its internal code tested.
[assembly: InternalsVisibleToAttribute("MyUnitTestedProject.UnitTests")]
Step 2. Change private to internal.
public class Course
{
internal Course()
{
}
public int Id { get; internal set; }
public string Name { get; internal set; }
public bool Open { get; internal set; }
public virtual ICollection<Student> Students { get; internal set; }
/* ... */
}
Step 3. Write your tests like normal!
[Fact]
public void CanRegisterStudentForOpenClass()
{
var course = new Course();
course.Id = "#####";
course.Register("Bob");
}
As a few people have mentioned here, unit testing something private is either a code smell, or a sign you're writing the wrong tests.
In this case, what you would want to do is use EF's in-memory database if you're using Core, or mocking with EF6.
For EF6 You can follow the docs here
I would say rather than newing your dbContext where you do, pass it in via Dependency Injection. If that's beyond the scope of the work you're doing, (I'm assuming this is actual coursework, so going to DI may be overkill) then you can create a wrapper class that takes a dbcontext and use that in place.
Taking a few liberties with where this code is called from...
class Semester
{
//...skipping members etc
//if your original is like this
public RegisterCourses(Student student)
{
using (var db = new SchoolContext())
{
RegisterCourses(student, db);
}
}
//change it to this
public RegisterCourses(Student student, SchoolContext db)
{
var course = db.Courses.Include(x => x.Students).Where(x => x.Name == courseName).First();
course.Register(studentName);
db.SaveChanges();
}
}
[Fact]
public void CanRegisterStudentForOpenClass()
{
//following after https://learn.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking#testing-query-scenarios
var mockCourseSet = new Mock<DbSet<Course>>();
mockCourseSet.As<IQueryable<Course>>().Setup(m => m.Provider).Returns(data.Provider);
mockCourseSet.As<IQueryable<Course>>().Setup(m => m.Expression).Returns(data.Expression);
mockCourseSet.As<IQueryable<Course>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockCourseSet.As<IQueryable<Course>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
//create an aditional mock for the Student dbset
mockStudentSet.As.........
var mockContext = new Mock<SchoolContext>();
mockContext.Setup(c => c.Courses).Returns(mockCourseSet.Object);
//same for student so we can include it
mockContext.Include(It.IsAny<string>()).Returns(mockStudentSet); //you can change the isAny here to check for Bob or such
var student = Institution.GetStudent("Bob");
var semester = Institution.GetSemester(Semester.One);
semester.RegisterCourses(student, mockContext);
}
If you're using EFCore you can follow it along from here
You can fake private constructors and members using TypeMock Isolator or JustMock (both paid) or using MS Fakes (only available in VS Enterprise).
There is also a free Pose library that allows you to fake access to properties.
Unfortunately, the private constructor can't be forged. Therefore, you will need to create an instance of the class using reflection.
Add package.
Open namespace:
using Pose;
Test code:
[Fact]
public void CanRegisterStudentForOpenClass()
{
var course = (Course)Activator.CreateInstance(typeof(Course), true);
ICollection<Student> students = new List<Student>();
Shim studentsPropShim = Shim.Replace(() => Is.A<Course>().Students)
.With((Course _) => students);
Shim openPropShim = Shim.Replace(() => Is.A<Course>().Open)
.With((Course _) => true);
int actual = 0;
PoseContext.Isolate(() =>
{
course.Register("Bob");
actual = course.Students.Count;
},
studentsPropShim, openPropShim);
Assert.Equal(1, actual);
}
You can create a JSON representation of your default instance and deserialize it with Newtonsoft.
Something like this:
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using privateConstructor;
namespace privateConstructorTest
{
[TestClass]
public class CourseTest
{
[TestMethod]
public void Register_WhenOpenIsTrue_EnableAddStudents()
{
// Arrange
const string json = #"{'Id': 1, 'name':'My Course', 'open':'true', 'students':[]}";
var course = CreateInstance<Course>(json);
// Act
course.Register("Bob");
// Assert
Assert.AreEqual(1, course.Students.Count);
}
[TestMethod]
public void Register_WhenOpenIsFalse_DisableAddStudents()
{
// Arrange
const string json = #"{'Id': 1, 'name':'My Course', 'open':'false', 'students':[]}";
var course = CreateInstance<Course>(json);
// Act
course.Register("Bob");
// Assert
Assert.AreEqual(0, course.Students.Count);
}
private static T CreateInstance<T>(string json) =>
JsonConvert.DeserializeObject<T>(json, new JsonSerializerSettings
{
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
ContractResolver = new ContractResolverWithPrivates()
});
public class ContractResolverWithPrivates : CamelCasePropertyNamesContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
if (prop.Writable) return prop;
var property = member as PropertyInfo;
if (property == null) return prop;
var hasPrivateSetter = property.GetSetMethod(true) != null;
prop.Writable = hasPrivateSetter;
return prop;
}
}
}
}
In order to have a cleaner test class, you can extract the JSON strings and the helper code that creates the instance.

How can I set the position variable only in it's parent rather than on every inherited class?

I currently have 4 classes that inherits Buildable class. For each derived class, I have to set it's position every time I inherits the buildable class. Is there any way to make this more cleaner? I think showing the code will make it more easier to understand.
public class BuildableData
{
public Vector3 position;
}
public class StockpileData : BuildableData
{
public int woodCount = 0;
public static StockpileData Create(Stockpile stockpile)
{
return new StockpileData
{
position = stockpile.transform.position,
woodCount = stockpile.WoodCount
};
}
}
public class HouseData : BuildableData
{
public static HouseData Create(House house)
{
return new HouseData
{
position = house.transform.position, // I'm talking about this one? I have to set it everytime I inherit BuildableData
};
}
}
Is there any way to make it automatically set by just passing the object to the constructor or maybe using reflection?
AutoMapper is the right library here to solve the mentioned problem as it helps to map one object's properties to another object's properties. Below code snippet will help to configure the same in your project.
using AutoMapper;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
});
var mapper = mappingConfig.CreateMapper();
var house = new House();
var houseData = mapper.Map<HouseData>(house);
var stockpile = new Stockpile();
var stockpileData = mapper.Map<StockpileData>(stockpile);
}
}
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<House, HouseData>()
.ForMember(destination => destination.Position,
source => source.MapFrom(m => m.transform.Position));
CreateMap<Stockpile, StockpileData>()
.ForMember(destination => destination.Position,
source => source.MapFrom(m => m.transform.Position));
}
}
}

C# Automapper How to resolve using property from customresolver

I am using the following mapping to map my data object to viewmodel object.
ObjectMapper.cs
public static class ObjectMapper
{
public static void Configure()
{
Mapper.CreateMap<User, UserViewModel>()
.ForMember(dest => dest.Title,
opt => opt.ResolveUsing<TitleValueResolver>())
.ForMember(dest => dest.Name,
opt => opt.ResolveUsing<NameValueResolver >())
.ForMember(dest => dest.ShortName,
opt => opt.ResolveUsing<ShortNameValueResolver >());
}
}
Parser
public class Parser{
public string GetTitle(string title){
/* add some logic */
return title;
}
public string GetName(string title){
/* add some logic */
return name;
}
public string GetShortName(string title){
/* add some logic */
return shortname;
}
}
AutoMapperCustomResolvers.cs
public class TitleValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public TitleValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetTitle(source.TITLE);
}
}
public class NameValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public NameValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetName(source.TITLE);
}
}
public class ShortNameValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public ShortNameValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetShortName(source.TITLE);
}
}
I am using the above code to add logic to the destination property using the separate custom value resolvers. Not sure is this the right approach.
i) Is there a better way to achieve this?
ii) And how to use unity to resolve in case i want to inject some dependency to custom resolver constructor?
Thanks
As I understand your question, you want to utilize a ValueResolver, that resolves multiple source properties into an intermediate data object, which is used to resolve multiple target properties. As an example, I assume the following source, target, intermediate and resolver types:
// source
class User
{
public string UserTitle { get; set; }
}
// target
class UserViewModel
{
public string VM_Title { get; set; }
public string VM_OtherValue { get; set; }
}
// intermediate from ValueResolver
class UserTitleParserResult
{
public string TransferTitle { get; set; }
}
class TypeValueResolver : ValueResolver<User, UserTitleParserResult>
{
protected override UserTitleParserResult ResolveCore(User source)
{
return new UserTitleParserResult { TransferTitle = source.UserTitle };
}
}
You need a target property in order to utilize opt.ResolveUsing<TypeValueResolver>(). This means, you can establish a mapping, where an appropriate target property is available.
So, for the moment, lets wrap the result into an appropriate container type:
class Container<TType>
{
public TType Value { get; set; }
}
And create a mapping
Mapper.CreateMap<User, Container<UserViewModel>>()
.ForMember(d => d.Value, c => c.ResolveUsing<TypeValueResolver>());
And another mapping
Mapper.CreateMap<UserTitleParserResult, UserViewModel>()
.ForMember(d => d.VM_Title, c => c.MapFrom(s => s.TransferTitle))
.ForMember(d => d.VM_OtherValue, c => c.Ignore());
And another mapping
Mapper.CreateMap<User, UserViewModel>()
.BeforeMap((s, d) =>
{
Mapper.Map<User, Container<UserViewModel>>(s, new Container<UserViewModel> { Value = d });
})
.ForAllMembers(c => c.Ignore());
// establish more rules for properties...
The last mapping is a bit special, since it relies on a nested mapping in order to update the destination with values from source via separately configured mapping rules. You can have multiple different transfer mappings for different properties by adding appropriate intermediate mappings and calls in BeforeMap of the actual mapped type. The properties that are handled in other mappings need to be ignored, since AutoMapper doesn't know about the mapping in BeforeMap
Small usage example:
var user = new User() { UserTitle = "User 1" };
// create by mapping
UserViewModel vm1 = Mapper.Map<UserViewModel>(user);
UserViewModel vm2 = new UserViewModel() { VM_Title = "Title 2", VM_OtherValue = "Value 2" };
// map source properties into existing target
Mapper.Map(user, vm2);
Dunno if this helps you. There might be better ways if you rephrase your question to describe your initial problem instead of what you suspect to be a solution.

Automapper ValueResolver with dependency resolved via StructureMap

This is what I am currently using (simplified and run in console app):
public class SomeValueResolver : ValueResolver<DateTime, long>
{
private readonly ISomeDependency _someDependency;
public SomeValueResolver(ISomeDependency _someDependency)
{
// ...
}
protected override long ResolveCore(DateTime source)
{
// ...
}
}
public class MyRegistry : Registry
{
public MyRegistry()
{
For<ISomeDependency >()
.Singleton()
.Use<SomeDependency>();
}
}
public static class AutoMapperConfiguration
{
public static void Configure(IContainer container)
{
Mapper.Initialize(cfg =>
{
cfg.ConstructServicesUsing(t => container.GetInstance(t));
cfg.AddProfile(new AutomapperProfile1());
});
}
}
public class AutomapperProfile1 : Profile
{
protected override void Configure()
{
CreateMap<Source, Target>()
.ForMember(dest => dest.Y, opt => opt.ResolveUsing<SomeValueResolver>().FromMember(e => e.X))
.IgnoreAllSourcePropertiesWithAnInaccessibleSetter();
}
}
public class Source
{
public DateTime X { get; set; }
}
public class Target
{
public DateTime Y { get; set; }
}
// main method
var container1 = new Container(new MyRegistry());
AutoMapperConfiguration.Configure(container1);
var source = new Source { X = DateTime.UtcNow };
var target = Mapper.Map<Target>(source);
Unfortunately, I get an exception along those lines:
Unable to create a build plan for concrete type SomeValueResolver
new SomeValueResolver(ISomeDependency)
? ISomeDependency= **Default**
1.) Attempting to create a BuildPlan for Instance of SomeValueResolver -- SomeValueResolver
2.) Container.GetInstance(SomeValueResolver)
Can this be resolved (pardon the pun).
I've tried your code with StructureMap 4.0.1.318 and Automapper 4.2.0.0.
I did get an exception which is different because related to bad conversion of DateTime to Int64.
I think you intended to write this :
public class Target
{
public long Y { get; set; }
}
By changing the type, the mapping works like a charm.
It's maybe related to the SomeDependency class which should possess a parameterless constructor to be resolved that way.

Categories

Resources