Map base class to specific implementation for collections - c#

Is it possible to map from a source-object to a specific implementation of a target object using automapper?
In the following code-sample, I want to map from SourceItem to TargetSampleItem and not to TargetBaseItem. Important: TargetBaseItem can't be abstract. But if I use the following mapper-configuration, always TargetBaseItem is used.
To summarize things up, the following collection should contain items of type TargetSampleItem, which is derriving from TargetBaseItem:
public IList<TargetItemBase> Items { get; set; } = new List<TargetItemBase>();
Complete code
using AutoMapper;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MapperTest
{
class Program
{
static void Main(string[] args)
{
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceRoot, TargetRoot>();
cfg.CreateMap<SourceItem, TargetItemBase>()
.IncludeAllDerived();
});
configuration.AssertConfigurationIsValid();
var mapper = configuration.CreateMapper();
var source = new SourceRoot
{
Id = 1,
Items = new List<SourceItem>
{
new SourceItem { A = "a1", B = "b1" },
new SourceItem { A = "a2", B = "b2" }
}
};
var result = mapper.Map<TargetRoot>(source);
// Should retur true
Console.WriteLine(result.Items.First() is TargetSampleItem);
Console.ReadLine();
}
/// <summary>
/// Source model
/// </summary>
public class SourceRoot
{
public int Id { get; set; }
public IList<SourceItem> Items { get; set; } = new List<SourceItem>();
}
public class SourceItem
{
public string A { get; set; }
public string B { get; set; }
}
/// <summary>
/// Target model
/// </summary>
public class TargetRoot
{
public int Id { get; set; }
public IList<TargetItemBase> Items { get; set; } = new List<TargetItemBase>();
}
public class TargetItemBase
{
public string A { get; set; }
}
public class TargetSampleItem : TargetItemBase
{
public string B { get; set; }
}
}
}
EDIT
using As<> is not working, because than AutoMapper is not mapping to the type, rather than just casting it:
cfg.CreateMap<SourceItem, TargetItemBase>()
.As<TargetSampleItem>();
EDIT 2/Solution
Using As<> is working, if a map between SourceItem and TargetSampleItem is added too:
cfg.CreateMap<SourceItem, TargetItemBase>().As<TargetSampleItem>();
cfg.CreateMap<SourceItem, TargetSampleItem>();

As does work for me:
cfg.CreateMap<SourceItem, TargetItemBase>().As<TargetSampleItem>();
cfg.CreateMap<SourceItem, TargetSampleItem>();

If As<> doesn't work for you, then a possible solution might be using AfterMap like -
CreateMap<SourceRoot, TargetRoot>()
.AfterMap((s, d) =>
{
s.Items.ToList().ForEach(p => d.TargetItems.Add(new TargetSampleItem { A = p.A, B = p.B }));
});
(Its not an elegant solution, but since TargetSampleItem is not the target of any of your maps, I don't see any reason why AutoMapper would create an instance of it).
You have to rename either of the Items properties so that AutoMapper doesn't try to map them automatically (I renamed the one in TargetRoot class to TargetItems). And of course you don't need the mapping between SourceItem and TargetItemBase.

Related

How to convert a class instance to JsonDocument?

Let's say we have an entity class that looks like this:
public class SerializedEntity
{
public JsonDocument Payload { get; set; }
public SerializedEntity(JsonDocument payload)
{
Payload = payload;
}
}
According to npsql this generates a table with column payload of type jsonb for this class which is correct.
Now what I would like to do is take any class instance and store it as payload in this table e.g.:
public class Pizza {
public string Name { get; set; }
public int Size { get; set; }
}
should then be possible to be retrieved as an object with following structure:
{Name: "name", Size: 10}
So I need something like this:
var pizza = new Pizza("Margharita", 10);
var se = new SerializedEntity(someConverter.method(pizza))
You can use JsonSerializer.SerializeToDocument which was added in .NET 6. In your case you would end up with this:
var pizza = new Pizza("Margharita", 10);
var se = new SerializedEntity(JsonSerializer.SerializeToDocument(pizza))
With System.Text.Json it's a little awkward but possible:
using System.Text.Json;
using System.Text.Json.Serialization;
var pizza = new Pizza("Margharita", 10);
var se = new SerializedEntity(JsonDocument.Parse(JsonSerializer.Serialize(pizza)));
It has been built-in to dotnet core since (I think) v3.0, so you do not need any additional 3rd party libs. Just don't forget the usings.
There may be some tricks to get the parsing a little more efficient, though (using async API, perhaps or as Magnus suggests by serializing to binary using SerializeToUtf8Bytes).
I haven't been able to find any approach that goes directly from T or object to JsonDocument. And I cannot believe this is not possible somehow. Please leave a comment if you know how that works or add your answer.
Serailize it and than parse it to JsonDocument.
var doc = JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(
new Pizza {Name = "Calzone", Size = 10}));
If your EF entity (SerializedEntity) will always have a Pizza as its serialized JSON document, then you can simply use POCO mapping - replace the JsonDocument property with a Pizza property, and map it to a jsonb column.
If the actual types you want vary (sometimes Pizza, sometimes something else), you can also map an object property to jsonb, and assign whatever you want to it. Npgsql will internally serialize any object to JSON:
class Program
{
static void Main()
{
using var ctx = new BlogContext();
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
ctx.FoodEntities.Add(new FoodEntity { SomeJsonFood = new Pizza { Name = "Napoletana" } });
ctx.FoodEntities.Add(new FoodEntity { SomeJsonFood = new Sushi { FishType = "Salmon" } });
ctx.SaveChanges();
}
}
public class BlogContext : DbContext
{
public DbSet<FoodEntity> FoodEntities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseNpgsql("...");
}
public class FoodEntity
{
public int Id { get; set; }
public string Name { get; set; }
[Column(TypeName = "jsonb")]
public object SomeJsonFood { get; set; }
}
public class Pizza
{
public string Name { get; set; }
public int Size { get; set; }
}
public class Sushi
{
public string FishType { get; set; }
}
Final solution for me:
public class SerializedEntity
{
public object? Payload { get; set; }
public SerializedEntity(object? payload)
{
Payload = payload;
}
}
and the EF configuration for it:
public void Configure(EntityTypeBuilder<SerializedEntity> builder)
{
builder.Property(n => n.Payload).HasColumnType("jsonb").HasConversion(
v => JsonConvert.SerializeObject(v,
new JsonSerializerSettings {NullValueHandling = NullValueHandling.Ignore}),
v => JsonConvert.DeserializeObject<object?>(v,
new JsonSerializerSettings {NullValueHandling = NullValueHandling.Ignore}));
}

Flatten object using auto mapper but not using ForMember or the naming conventions used for flattening

Problem
Flatten large, multi level object using auto mapper to allow me to pass values to sql using Dapper.
Options
After reading the docs and various answers on SO, there seems to be two options, using ForMember or using NamingConventions. Neither of these can work for me as the object is so large that ForMember would require almost as much manual mapping. NamingConventions would mean some unreadable and irrelevant names for where the target object is being used.
refs I used: ForMember flattening
I did find a really basic example of using the ITypeConverter (Custom type converters) and have tried to make it work for me, but I'm really struggling, after three days of trying, I thought I should turn for help.
After searching various forums I decided to try and create a generic solution as I will be reusing this multiple times throughout my project.
Please note, if there is a better way (an existing extension, or a configuration i've missed, i'd love to hear of it. If not could you please offer some guidance on how to complete my task - I feel like I'm banging my head against a brick wall)
below I have added the console app I created to try to resolve this (I feel I should say that I know this code is awful, I have just been hacking away in the hope something would work)
using AutoMapper;
using System;
using System.Linq;
using System.Reflection;
namespace AutomapperTests
{
public class Program
{
static void Main(string[] args)
{
var property = Program.CreateProperty();
var config = new MapperConfiguration(cfg => cfg.CreateMap<PropertyDto, FlatProperty>()
.ConvertUsing<MyTypeConverter<PropertyDto, FlatProperty>>());
var mapper = config.CreateMapper();
var flatProperty = mapper.Map<FlatProperty>(property);
}
private static PropertyDto CreateProperty()
{
var property = new PropertyDto();
property.Guid = new Guid();//not mapping to guid
property.SomeTestVal = 123456;
property.TransactionStatus = TransactionStatus.ForSale;
property.Address = new AddressDto();
property.Detail = new DetailDto();
property.Address.PostCode1 = "ng10";
property.Detail.LandArea = 12;
return property;
}
}
public class MyTypeConverter<T, TK> : ITypeConverter<T, TK>
{
public TK Convert(T source, TK destination, ResolutionContext context)
{
var sourceType = source.GetType();
PropertyInfo[] sourceClassProperties = sourceType.GetProperties();//note: i've tried flattening the structure at this point but failed
var properties = GetCustomObjectsFromProperties(sourceType.GetProperties());
destination = (TK)Activator.CreateInstance(typeof(TK));
var destinationType = destination.GetType();
PropertyInfo[] destinationProperties = destinationType.GetProperties();
foreach (PropertyInfo sourceClassProperty in sourceClassProperties)
{
SetValue(sourceClassProperty, source, destinationProperties, destination);
}
return destination;
}
private void SetValue(PropertyInfo sourceClassProperty, T source, PropertyInfo[] destinationProperties, TK destination)
{
bool isNativeClass = IsNativeClass(sourceClassProperty);
object sourceValue = sourceClassProperty.GetValue(source);
string sourceName = sourceClassProperty.Name;
PropertyInfo destProperty = destinationProperties.FirstOrDefault(x => x.Name == sourceName);
if (destProperty == null && !isNativeClass)
{
//note: my idea was to use recursion to enter the object if necessary and search for a matching value ---maybe i'm really overthinking this???
}
SetDestinationValue(destProperty, destination, sourceValue);
}
private bool IsNativeClass(PropertyInfo sourceClassProperty)
{
return sourceClassProperty.GetType().Namespace == "System";
}
private void SetDestinationValue(PropertyInfo destProperty, TK destination, object sourceValue)
{
if (destProperty != null)
{
destProperty.SetValue(destination, sourceValue);
}
}
private PropertyInfo[] GetCustomObjectsFromProperties(PropertyInfo[] propertyInfo)
{
return propertyInfo.Where(info => info.Module.Name == GetAssemblyName()).ToArray();
}
private string GetAssemblyName()
{
return $"{typeof(T).Assembly.GetName().Name}.dll";
}
}
public class FlatProperty
{
public Guid Guid { get; set; }
public int SomeTestVal { get; set; }
public int? TransactionStatusId { get; set; }
public string Postcode1 { get; set; }
public decimal? LandArea { get; set; }
}
public class PropertyDto
{
public Guid Guid { get; set; }
public int SomeTestVal { get; set; }
public TransactionStatus? TransactionStatus { get; set; }
public AddressDto Address { get; set; }
public DetailDto Detail { get; set; }
}
public class AddressDto
{
public string PostCode1 { get; set; }
}
public class DetailDto
{
public decimal? LandArea { get; set; }
}
public enum TransactionStatus
{
Sold = 1,
ForSale = 2
}
}
Edit:
I have just found that it seems I can do something like this:
in config:
CreateMap<AddressDto, CreatePropertyDataModel>();
CreateMap<DetailDto, CreatePropertyDataModel>();
CreateMap<PropertyDto, CreatePropertyDataModel>()
.ForMember(dest => dest.Guid,
opts => opts.MapFrom(
src => src.Guid.ToString()));
set an object:
var property = new PropertyDto();
property.Guid = new Guid();//not mapping to guid
property.SomeTestVal = 123456;
property.TransactionStatus = TransactionStatus.ForSale;
property.Address = new AddressDto();
property.Detail = new DetailDto();
property.Address.PostCode1 = "ng10";
property.Detail.LandArea = 12;
return property;
in some class:
var dataModel = new UpsertPropertyDataModel();
_mapper.Map(_property, dataModel);
_mapper.Map(_property.Address, dataModel);
_mapper.Map(_property.Detail, dataModel);
My problem with this is that I get the error Unmapped members were found, these unmapped members are reffering to members Address or Detail could not map to, so I would have to set those members to Ignore(), is this a viable solution that i'm using incorrectly? (please note my real world objects are much bigger)

C# List property unresponsive to adding new elements

Would you help me understand why before exiting the Main function the repository.Courses is null, even though I had just added a course to the repository by calling repository.AddCourse(course); before exiting? How would I correct this? I think it may have to do with how the Courses property is defined (getter and setter). In teh getter I want to initialize by an empty list only if it's already null, otherwise I want to return the existing list. In the setter I want to assign a value - this should be correlated with the possibility of adding to list.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Repo
{
public IList<Course> _courses;
public IList<Course> Courses
{
get
{
if (_courses == null)
_courses = new List<Course>();
return new List<Course>(_courses);
}
set
{
_courses = value;
}
}
public void AddCourse(Course course)
{
course.Id = Courses.NextId();
Courses.Add(course);
}
static void Main(string[] args)
{
Repo repository = new Repo();
var path = "C:\\a1\\demos\\demo1-after\\ConsoleApplication1\\json1.json";
var reader = new StreamReader(path);
string text = reader.ReadToEnd();
var course = JsonConvert.DeserializeObject<Course>(text);
repository.AddCourse(course);
}
}
public class Course : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Author { get; set; }
public Course(string n, string a)
{
Name = n;
Author = a;
}
}
public interface IEntity
{
int Id { get; set; }
}
public static class Extensions
{
public static int NextId<T>(this IList<T> list) where T : IEntity
{
return list.Any() ? list.Max(x => x.Id) + 1 : 0;
}
}
}
The problem here is that your get is always returning a new list, rather than the backing field.
Try this instead:
get
{
if (_courses == null) _courses = new List<Course>();
return _courses;
}
I noticed that the getter of Courses returns a new list rather than a reference to the field. Is that because you don't want anyone from outside the class to be able to add anything to the list except through AddCourse?
In this case the fix I'd recommend is to change AddCourse to address the field directly rather than the property, as currently it's adding the item to the new list created by the getter rather than to the list referenced by your field.

Setting data in multiple nested lists ASP.NET MVC C#

I'm an intern with very basic knowledge of ASP and C#.
I'm trying to display a list of projects > maps > thememaps in an application I'm working on in ASP.NET MVC. In my third foreach I get an error saying: "Does not contain a definition for "ThemeMaps" and no extension method "ThemeMaps" accepting a first argument of type could be found".
I'm confused as to why vmProject.Maps does not contain the property ThemeMaps. I instantiated that list just like maps. What am I doing wrong?
LayersController.cs
// Create viewmodel object
var viewModel = new AddLayerToThemeMapViewModel();
// Create Project list
viewModel.Projects = new List<AddProject>();
// Loop over all maps
List<Project> projects = this.applicationDb.Projects.OrderBy(e => e.Title).ToList();
foreach (var project in projects)
{
// Create map
var vmProject = new AddProject()
{
ProjectId = project.ProjectID,
ProjectTitle = project.Title,
Maps = new List<AddLayerMap>(),
};
foreach (var map in project.Maps.OrderBy(e => e.Title))
{
// Create map
vmProject.Maps.Add(new AddLayerMap()
{
MapId = map.MapId,
MapTitle = map.Title,
ThemeMaps = new List<AddLayerMapThemeMap>(),
});
// Loop over all thememaps in map
foreach (var thememap in map.ThemeMaps.OrderBy(e => e.Order))
{
vmProject.Maps.ThemeMaps.Add(new AddLayerMapThemeMap()
{
ThemeMapId = thememap.ThemeMapId,
ThemeMapTitle = thememap.Title,
});
}
}
// Add map to list
viewModel.Projects.Add(vmProject);
}
My viewmodel class
using Mapgear.MapViewer.Entities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Mapgear.MapViewer.ViewModels
{
public class AddLayerToThemeMapViewModel
{
public Guid LayerId { get; set; }
public List<AddProject> Projects { get; set; }
}
public class AddProject
{
public Guid ProjectId { get; set; }
public string ProjectTitle { get; set; }
public List<AddLayerMap> Maps { get; set; }
}
public class AddLayerMap
{
public Guid MapId { get; set; }
public string MapTitle { get; set; }
public List<AddLayerMapThemeMap> ThemeMaps { get; set; }
}
public class AddLayerMapThemeMap
{
public Guid ThemeMapId { get; set; }
public string ThemeMapTitle { get; set; }
}
}
I made a scetch before I started on paper, which looks like the following:
LayerId
List project
ProjectId
ProjectTitle
List Map
MapId
MapTitle
List ThemeMap
ThemeMapId
ThemeMapTitle
I know my class names are a bit out of wack, however I din't write them myself. Gonna optimize them after.
PS: This is my first question on StackOverflow!
I modified the code in the second for-each loop a bit. I added a variable for the newly created map and add the thememaps to this created map. Check if it's correct.
var newMap = new AddLayerMap()
{
MapId = map.MapId,
MapTitle = map.Title,
ThemeMaps = new List<AddLayerMapThemeMap>(),
};
// Create map
vmProject.Maps.Add(newMap);
// Loop over all thememaps in map
foreach (var thememap in map.ThemeMaps.OrderBy(e => e.Order))
{
newMap.ThemeMaps.Add(new AddLayerMapThemeMap()
{
ThemeMapId = thememap.ThemeMapId,
ThemeMapTitle = thememap.Title,
});
}

Entity Framework 5 Won't Fetch Relationships With Include()

I am quite certain that questions like this have been answered a number of times before, but I can't get any of the suggestions to work.
I am building a MVC 4 application with Entity Framework 5, where the entities were generated from existing tables. I have entity classes that look like this:
namespace RebuildingModel
{
using System;
using System.Collections.Generic;
public partial class StandardCodeTable
{
public StandardCodeTable()
{
this.StandardCodeTableTexts = new HashSet<StandardCodeTableText>();
}
public int TableCode { get; set; }
public string RefTableName { get; set; }
public virtual ICollection<StandardCodeTableText> StandardCodeTableTexts { get; set; }
}
}
namespace RebuildingModel
{
using System;
using System.Collections.Generic;
public partial class StandardCodeTableText
{
public int TableCode { get; set; }
public string LanguageCode { get; set; }
public string TextVal { get; set; }
public virtual StandardCodeTable StandardCodeTable { get; set; }
}
}
namespace RebuildingSite.Models
{
public class CodeTableJoined
{
public int TableCode { get; set; }
public string ReferenceTableName { get; set; }
public string LanguageCode { get; set; }
public string TextValue { get; set; }
}
}
I have a DAO that looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RebuildingModel.Dao
{
public class CodeTableDao
{
public CodeTableDao() { }
public ISet<StandardCodeTableText> GetCode(string refTableName)
{
HashSet<StandardCodeTableText> codes = new HashSet<StandardCodeTableText>();
using (var db = new RebuildingTogetherEntities())
{
db.StandardCodeTableTexts.Include("StandardCodeTables");
var query = from c in db.StandardCodeTableTexts
where c.StandardCodeTable.RefTableName == refTableName
orderby c.TableCode
select c;
foreach (var item in query)
{
codes.Add(item);
}
}
return codes;
}
}
I have a controller that looks like this:
namespace RebuildingSite.Controllers
{
public class CodeTableController : Controller
{
public ActionResult Index(string refTableName)
{
CodeTableDao dao = new CodeTableDao();
ICollection<StandardCodeTableText> codes = dao.GetCode(refTableName);
HashSet<CodeTableJoined> joins = new HashSet<CodeTableJoined>();
foreach (var code in codes)
{
CodeTableJoined join = new CodeTableJoined();
join.TableCode = code.TableCode;
join.LanguageCode = code.LanguageCode;
join.TextValue = code.TextVal;
join.ReferenceTableName = code.StandardCodeTable.RefTableName;
joins.Add(join);
}
ISet<string> refTableNames = dao.GetReferenceTables();
ViewBag.RefTableNames = refTableNames;
return View(joins);
}
}
}
When I run the view attached to the controller, an ObjectDisposedException is thrown at this line, where the relationship is used:
join.ReferenceTableName = code.StandardCodeTable.RefTableName;
This has to be something simple. What am I doing wrong? I have tried adding that Include() call in from the context in many different places, even multiple times.
I've also tried adding an explicit join in the Linq query. I can't get EF to fetch that relationship.
Copying my comment to an answer - Put the include be in the actual query
var query = from c in
db.StandardCodeTableTexts.include("StandardCodeTables"). where
c.StandardCodeTable.RefTableName == refTableName orderby c.TableCode
select c;

Categories