Ignoring items in collection with Automapper when mapping collection - c#

I am mapping an collection of items
var List<A> myCollection = new List<A>();
public class A
{
bool HasChanges {get;set;}
}
var mappedCollection = Map(myCollection);
then I only want to map the items where HasChanges == true
Is this possible?

With Linq:
var mappedCollection = Map(myCollection.Where(x => x.HasChanges == true).ToList());

Automapper has custom type converters:
https://github.com/AutoMapper/AutoMapper/wiki/Custom-type-converters
// this is your converter
public class ATypeConverter : ITypeConverter<string, A>
{
public A Convert(ResolutionContext context)
{
// implement conversion logic
}
}
// add this in a bootstrapper in your app
Mapper.CreateMap<string, A>().ConvertUsing<ATypeConverter>();
now after you Map your objects it will convert them using your custom mapper, which will allow you to skip the items with change.

Related

Automapper IEnumerable within class is not being mapped to RepeatedField

I want to map between two classes:
public class A {
public IEnumerable<C> someList
}
and
public class B {
public RepeatedField<D> someList
}
where RepeatedField is a class from Google.Protobuf.Collections that handles gRPC data.
EDIT: As it turns out, the way that gRPC creates classes via its prototype is not exactly like creating a class like B. See my answer.
I create an Automapper MappingConfiguration like this
return new MapperConfiguration(cfg =>
{
cfg.CreateMap<C, D>().ReverseMap();
cfg.CreateMap<A, B>().ReverseMap();
});
and then it gets registered via ASP.NET Startup class.
If I do something like this in another class
A instanceA; // assume A's list has values inside
var listofD = this.mapper.Map<List<D>>(A.someList)
it correctly returns a list with values inside. However:
A instanceA; // assume A's list has values inside
B instanceB = this.mapper.Map<B>(A);
returns an instance of B, but the list inside of instanceB is empty. How do I fix this?
You need to create a custom type converter for performing the conversion:
private class EnumerableToRepeatedFieldTypeConverter<TITemSource, TITemDest> : ITypeConverter<IEnumerable<TITemSource>, RepeatedField<TITemDest>>
{
public RepeatedField<TITemDest> Convert(IEnumerable<TITemSource> source, RepeatedField<TITemDest> destination, ResolutionContext context)
{
destination = destination ?? new RepeatedField<TITemDest>();
foreach (var item in source)
{
// obviously we haven't performed the mapping for the item yet
// since AutoMapper didn't recognise the list conversion
// so we need to map the item here and then add it to the new
// collection
destination.Add(context.Mapper.Map<TITemDest>(item));
}
return destination;
}
}
And the other way, if required:
private class RepeatedFieldToListTypeConverter<TITemSource, TITemDest> : ITypeConverter<RepeatedField<TITemSource>, List<TITemDest>>
{
public List<TITemDest> Convert(RepeatedField<TITemSource> source, List<TITemDest> destination, ResolutionContext context)
{
destination = destination ?? new List<TITemDest>();
foreach (var item in source)
{
destination.Add(context.Mapper.Map<TITemDest>(item));
}
return destination;
}
}
Which you can register like so:
ce.CreateMap(typeof(IEnumerable<>), typeof(RepeatedField<>)).ConvertUsing(typeof(EnumerableToRepeatedFieldTypeConverter<,>));
ce.CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListTypeConverter<,>));
Try it online
I've solved the issue.
A Google.Protobuf.Collections.RepeatedField inside a C# class is readonly, meaning that directly assigning values into it won't work and will only return an empty list on the way back. Therefore, I created a custom type converter between the two larger classes to bring them together. What it does is add values directly into the RepeatedField rather than populating my own RepeatedField and assigning the value into the class.
public static class mapConfig
{
public static ContainerBuilder RegisterObjectMappers(this ContainerBuilder builder)
{
builder.Register(c => GetV1MapperConfiguration().CreateMapper())
.As<IMapper>().SingleInstance();
return builder;
}
private static MapperConfiguration GetMapConfig()
{
return new MapperConfiguration(cfg =>
{
// some mappings here
cfg.CreateMap<C, D>().ReverseMap();
cfg.CreateMap<A, B>().ConvertUsing<AToBConverter>();
});
}
}
public class AToBConverter : ITypeConverter<A, B>
{
public B Convert(A source, B destination, ResolutionContext context)
{
var b = new B
{
// internal values here aside from the repeated field(s)
};
// Need to use the Add method to add values rather than assign it with an '=' sign
foreach (var someValue in source.someList)
{
b.someList.Add(context.Mapper.Map<D>(someValue));
}
return b;
}
}
Converting from RepeatedField to a List or any other IEnumerable in a mapped class isn't any trouble and didn't require another converter for me.

Get DbContext's DbSet<T> where T is variable

I have a DbContext looking somewhat like this:
class MyDbContext: DbContext
{
public DbSet<Class1> Set1 {get;set;}
public DbSet<Class2> Set2 {get;set;}
...
}
where Class1, Class2 ... : BaseClass
The thing is I'm reading data from xml and I use a dictionary that looks like this:
public class XmlNode
{
public Func<BaseClass> CreateEntity { get; set; }
...
}
public static Dictionary<string, XmlNode> Nodes = new Dictionary<string, XmlNode>()
{
["Tag1"] = new XmlNode()
{
CreateEntity = () => new Class1(),
}
...
}
And then I have to compare the read entity to an existing table and maybe add it. But I can't find a way to get the approptiate table without making a different function for every Class I have. Is there a way to get a DbSet where Class is a variable?
The DbContext your data context derives from has a method called Set(Type t) that accepts a type, type can be created from string.
For the scenario you've described you can create a DbSet from the string in your XML by
var typeName = "Some.Namespace.AndType";
DbSet t = Set(Type.GetType(typeName));
Note that you can't use linq or lambda expressions to query the resulting object, unless you cast it to a typed DbSet<T> or use a library like System.Linq.Dynamic which would allow you to call t.AsQueryable().Where("SomeProperty == #value");, but this should get you started.
From how to check whether dbcontext sett exists in model
you can just check if it exist then get DBset after checked
if(Exists<yourentity>())
{
... TEntity exist
}
from link
public bool Exists<TEntity>() where TEntity : class
{
string entityName = typeof(TEntity).Name;
ObjectContext objContext = ((IObjectContextAdapter)this).ObjectContext;
MetadataWorkspace workspace = objContext.MetadataWorkspace;
return workspace.GetItems<EntityType>(DataSpace.CSpace).Any(e => e.Name == entityName);
}

Automapper map array of objects

I'm trying to map an array of objects containing source classes, which has to be mapped to an array of objects containing destination classes. But It does work out of the box for my code.
class Class1ChildClass
{
public int Value { get; set; }
}
class Class1
{
// This array contains classes of type Class1ChildClass
public object[] ClassesAsObjects { get; set; }
}
class Class2ChildClass
{
public int Value { get; set; }
}
class Class2
{
// This array should contain classes of type Class2ChildClass
public object[] ClassesAsObjects { get; set; }
}
The straight-forward way in mind mind would be this code:
var cl1 = new Class1
{
ClassesAsObjects =
new object[] {
new Class1ChildClass
{
Value = 999
}
}
};
var config =
new MapperConfiguration(
cfg =>
{
cfg.CreateMap<Class1ChildClass, Class2ChildClass>();
cfg.CreateMap<Class1, Class2>();
}
);
var mapper = config.CreateMapper();
var cl2 = mapper.Map<Class2>(cl1);
Whatever I do, I always get an array of Class1ChildClass in the destination class Class2.
I tried to use ForMember with no success.
I do not know why you want to use Automapper in this case instead of just manually doing the mapping. From my understanding, Automapper looks at the types and their properties, and does its magic with those.
However in your case, there is not much to leverage Automapper. You are using object which has no Value property. You want to map members of object[] but actually want the content to be somehow converted from one (object sub-)class to another.
But if you want to use Automapper, you can use the AfterMap to map the ClassesAsObjects member:
var config =
new MapperConfiguration(
cfg =>
{
cfg.CreateMap<Class1, Class2>()
.AfterMap((src, dest) =>
{
dest.ClassesAsObjects = src.ClassesAsObjects
.Select(x =>
{
return (x is Class1ChildClass)
? (object)(new Class2ChildClass { Value = (x as Class1ChildClass).Value })
: null;
}).ToArray();
});
}
);
According to this: https://github.com/AutoMapper/AutoMapper/wiki/Lists-and-arrays
AutoMapper still requires explicit configuration for child mappings, as AutoMapper cannot "guess" which specific child destination mapping to use.
So you may need to add something like this:
Mapper.Initialize(c=> {
c.CreateMap<Class1, Class2>()
.Include<Class1ChildClass, Class2ChildClass>();
c.CreateMap<Class1, Class2>();
});

How to query all tables that implement an interface

I have implemented an interface for some of my entity classes:
public partial class Order : IReportable
{
public string TableName { get { return "Order"; } }
}
public partial class Client: IReportable
{
public string TableName { get { return "Client"; } }
}
public interface IReportable
{
string TableName { get; }
}
Then I added this to the DbContext:
public virtual DbSet<IReportable> IReportable { get; set; }
When I try to query all the tables that implement this interface (as shown here):
var result = from reportabletable in db.IReportable
where reportabletable.TableName == table_name
select reportabletable
I get the following exception:
The type 'Report.DataAccess.IReportable' was not mapped. Check that
the type has not been explicitly excluded by using the Ignore method
or NotMappedAttribute data annotation. Verify that the type was
defined as a class, is not primitive or generic, and does not inherit
from EntityObject.
I would go for something like this:
Create this extension method
public static class DbContextExtensions
{
public static IEnumerable<T> SetOf<T>(this DbContext dbContext) where T : class
{
return dbContext.GetType().Assembly.GetTypes()
.Where(type => typeof(T).IsAssignableFrom(type) && !type.IsInterface)
.SelectMany(t => Enumerable.Cast<T>(dbContext.Set(t)));
}
}
And use it like this:
using (var db = new dbEntities())
{
var result = from reportabletable in db.SetOf<IReportable>()
where reportabletable.TableName == table_name
select reportabletable
}
EF doesn't like mapping interfaces directly to tables. You can get around this by making using a generic Repository, as outlined Here!
Then use repository method and supply the Type of the table(s) you want to query. Something like: myRepo.GetAll<myClient.GetType()>();
Get the classes that inherit that interface and run the query for all of them:
var types = System.Reflection.Assembly.GetExecutingAssembly().GetTypes().Where(mytype => mytype .GetInterfaces().Contains(typeof(myInterface)));
foreach (var mytype in types)
{ // aggregate query results }
Hope this helps! There is probably a more graceful solution
First of all MarcGravell comment is on the money. Its up to you to know which table to query.
Personally I go through list of poco types that implement an interface or have an custom attribute. But if you are keen to go via the DBContext only, here are some extensions that give you access to the "names". You will still need to access that part of the context afterwards one at a time.
Again you can do that via generics, but you can just go directly as you suggest.
You will need to iterate of a list of types.
eg:
ReportRespository : BaseRespository where t : IReport
Check the assembly for Certain types and attributes
eg
/// <summary>
/// POCOs that have XYZ Attribute of Type and NOT abstract and not complex
/// </summary>
/// <returns></returns>
public static List<Type> GetBosDirDBPocoList() {
var result = new List<Type>();
// so get all the Class from teh assembly that public non abstract and not complex
foreach (var t in Assembly.GetExecutingAssembly().GetTypes()
.Where(t => t.BaseType != null
&& t.IsClass
&& t.IsPublic
&& !t.IsAbstract
&& !t.IsComplexType()
&& t.GetMyAttribute() != null)) {
result.Add(t);
}
}
return result;
}
public static GetMyAttribute(this Type T) {
var myAttr= T.GetCustomAttributes(true)
.Where(attribute => attribute.GetType()
.Name == "XYZAttr").Cast<BosDir>().FirstOrDefault();
return myAttr;
}
Extensions
public static class DalExtensions {
// DbSet Names is the plural property name in the context
public static List<string> GetModelNames(this DbContext context) {
var propList = context.GetType().GetProperties();
return GetDbSetNames(propList);
}
// DbSet Names is the plural property name in the context
public static List<string> GetDbSetTypeNames<T>() where T : DbContext {
var propList = typeof (T).GetProperties();
return GetDbSetNames(propList);
}
// DBSet Types is the Generic Types POCO name used for a DBSet
public static List<string> GetModelTypes(this DbContext context) {
var propList = context.GetType().GetProperties();
return GetDbSetTypes(propList);
}
// DBSet Types POCO types as IEnumerable List
public static IEnumerable<Type> GetDbSetPropertyList<T>() where T : DbContext {
return typeof (T).GetProperties().Where(p => p.PropertyType.GetTypeInfo()
.Name.StartsWith("DbSet"))
.Select(propertyInfo => propertyInfo.PropertyType.GetGenericArguments()[0]).ToList();
}
// DBSet Types is the Generic Types POCO name used for a DBSet
public static List<string> GetDbSetTypes<T>() where T : DbContext {
var propList = typeof (T).GetProperties();
return GetDbSetTypes(propList);
}
private static List<string> GetDbSetTypes(IEnumerable<PropertyInfo> propList) {
var modelTypeNames = propList.Where(p => p.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
.Select(p => p.PropertyType.GenericTypeArguments[0].Name)
.ToList();
return modelTypeNames;
}
private static List<string> GetDbSetNames(IEnumerable<PropertyInfo> propList) {
var modelNames = propList.Where(p => p.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
.Select(p => p.Name)
.ToList();
return modelNames;
}
}
}
Accepted solution does not work in EF Core.
Here is my first working draft
public IEnumerable<T> SetOf<T>() where T : class
{
var firstType = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes())
.FirstOrDefault(type => typeof(T).IsAssignableFrom(type) && !type.IsInterface);
if (firstType == null) return new List<T>();
var dbSetMethodInfo = typeof(DbContext).GetMethod("Set");
var dbSet = dbSetMethodInfo.MakeGenericMethod(firstType);
IQueryable<T> queryable = ((IQueryable)dbSet.Invoke(this, null)).Cast<T>();
return queryable.ToList().Cast<T>();
}
Then you could use like this
_dbContext.SetOf<ISomeInterface>();
More info here Expose method DbContext.Set(Type entityType)

How to automatically set the parent object in a child property with autofixture

I want to use autofixture to create an object graph where children have a reference to the parent object. For example :
class A
{
public List<B> Children { get; set; }
}
class B
{
public A Parent { get; set; }
}
I tried to make a behavior that handles the recursion, but I don't know how to emit the parent object as the content of the property.
public class AutoParentOnRecursionBehavior : ISpecimenBuilderTransformation
{
public ISpecimenBuilder Transform(ISpecimenBuilder builder)
{
if (builder == null)
throw new ArgumentNullException("builder");
return new RecursionGuard(builder, new AutoParentOnRecursionHandler());
}
}
public class AutoParentOnRecursionHandler : IRecursionHandler
{
public object HandleRecursiveRequest(
object request,
IEnumerable<object> recordedRequests)
{
object handleRecursiveRequest = recordedRequests.First(x => x.Equals(request));
return ....
}
}
Thanks.
Edit :
I am thinking of a generic way, without having to specify the types A and B or even the property Children.
For all properties of a property type that contains the object, set them to the parent object. In other words all properties of a type that trigger the recursion guard set them to the last object in the creation hierarchy.
My answer assumes that
B.Parent should be null if you create B directly.
B.Parent should be set to the instance of A that contains it, if you create A.
This can be achieved with the following rather simple customizations when using PostProcessorFor:
fixture.Customize<B>(c => c.Without(x => x.Parent));
fixture.PostProcessorFor<A>(a => { foreach(var b in a.Children) b.Parent = a; });
Some asserts to illustrate the result:
var b = fixture.Create<B>();
Assert.Null(b.Parent);
var a = fixture.Create<A>();
Assert.True(a.Children.All(b => ReferenceEquals(b.Parent, a)));

Categories

Resources