Automapper: update collection - c#

Code:
[Fact(DisplayName = "")] public void Test1()
{
var definition = new Definition
{
Hash = "hash1",
Link = "link1",
Name = "name1"
};
var view = new View
{
Hash = "hash2",
Id = Guid.Parse("ab8a6aac-532d-43af-aa0a-3781f0da3d96"),
Link = null,
Name = "name2"
};
var views = new List<View>
{
view
};
var definitions = new List<Definition>
{
definition
};
var collectionsMapping =_mapper.Map(definitions, views);
var simpleMapping =_mapper.Map(definition, view);
}
Profile settings:
CreateMap<ReadFileDefinition, StandardFileView>();
Destination class:
public class View
{
public Guid? Id{get;set;}
public string? Name { get; init; }
public string? Hash { get; set; }
public string? Link { get; set; }
}
Source class:
public class Definition
{
public string Name { get; set; }
public string Hash { get; set; }
public string Link { get; set; }
}
As you can see, class 'Definition' is missing property 'Id'. When you try to update property 'Id' for the collection 'List<View>'(see 'collectionsMapping'), it is always null.
But for 'simpleMapping' it works fine and 'Id' keep value from destination object.
(collectionsMapping result)
(simpleMapping result)
Question: How do i update property 'Id' for each collection member so that it stays from destination collection views?
Thx.

Mapping a single item will use the given destination instance and will not touch any unmapped properties (here the Id property).
Mapping a collection will create a new destination item for each source item and there you have the given default value from the class definition for any unmapped property.
You may want to customize the mapping to set the Id property in this case.

Related

Nesting list in model and the viewing and storing data

Sorry if this is a simple question but if I want to have a list inside a model, and later access and set the values of the list?
Say my main model looks like this:
public class StartPageModel : IPageViewModel<StartPage>
{
public IList<ListContent> ListContent { get; set; }
public StartPage CurrentPage { get; set; }
}
public class ListContent
{
public IList<ListElement> ArticleListContent { get; set; }
public IList<ListElement> InsightListContent { get; set; }
}
How can I set the ArticleListContent list to a value by referencing the parent model?
public ActionResult Index(StartPage currentPage)
{
var model = new StartPageModel(currentPage);
model.ListContent.ArticleListContent = GetListContent(currentPage.ArticleCollection);
}
However this returns the error:
IList does not contain a definition for 'ArticleListContent'
I'm not sure you require a collection of ListContent in your StartPageModel, correct me if I'm wrong.
Change
public IList<ListContent> ListContent { get; set; }
to
public ListContent ListContent { get; set; }
And provided ListContent is initialized, your assignment will work.
It's because it's referencing the List of ListContent, not an individual item in that list. Here's some examples:
var model = new StartPageModel(currentPage);
model.ListContent[0].ArticleListContent = GetListContent(currentPage.ArticleCollection); // Access first in list
model.ListContent[1].ArticleListContent = GetListContent(currentPage.ArticleCollection); // Access secondin list
model.ListContent.First().ArticleListContent = GetListContent(currentPage.ArticleCollection); // Access first in list using Linq

How to change class/property name?

For example:
public class Car{
public string color {get; set;}
public int VINCode {get;set;}
}
Now if I call nameof(Car) it returns "Car"
[Name("something")]
public class Car{
[Name("something_else")]
public string color {get; set;}
public int VINCode {get;set;}
}
But how can I get nameof to return the value in the Name attribute rather than the name of the class or method. eg: nameof(Car) == "something" or nameof(Car.color) == "something_else".
the problem:
var modelState = JsonConvert.DeserializeObject<Dictionary<string, List<string>>>(data);
var departmentViewModels = modelState[nameof(DepartmentListView.DepartmentsViewModels)][0];
var departmentTypes = modelState[nameof(DepartmentListView.DepartmentsViewModels)][0];
fixing for that:
var modelState = JsonConvert.DeserializeObject<DepartmentListView>(data);
var departmentViewModels = modelState.DepartmentsViewModels;
var departmentTypes = modelState.DepartmentTypes;
Serialization of this:
public class DepartmentListView
{
public IEnumerable<DepartmentViewModel> DepartmentsViewModels { get; set; }
public IEnumerable<DepartmentType> DepartmentTypes { get; set; }
}
will be:
departmentsViewModel : [], departmentTypes : [] (with lowercase)
I know I can change that lowercase serialization with JsonProperty, but I thought that I will can change the name of class or property...
I'm afraid you cannot do it with nameof.
But if you want to get the value of a CustomAttribute you can try this:
// I assume this is your Name class
public class Name: Attribute
{
public string Data { get; }
public Name(string data) { Data = data; }
}
Then you can
// Will return "something"
var classAttrData = ((Name) typeof(Car).GetCustomAttribute(typeof(Name))).Data;
// Will return "something_else"
var fieldAttrData = ((Name) typeof(Car).GetField(nameof(Car.color)).GetCustomAttribute(typeof(Name))).Data;
It's look like you are asking about your attempted solution rather than your actual problem.
The solution to get new name of class / property:
Create your class with DisplayNameAttribute as follow:
[DisplayName("something")]
public class Car
{
[DisplayName("something_else")]
public string color { get; set; }
public int VINCode { get; set; }
}
To get your class attribute name:
var attributes = typeof(Car) // type declaration
.CustomAttributes.FirstOrDefault() // first item of collection that contains this member's custom attributes like [DisplayName("something")]
?.ConstructorArguments.FirstOrDefault() // first item of structure collecion
.Value; // value as string - "something"
Similarly to the property name, you only need to get its type first
var attribute = typeof(Car).GetProperty(nameof(Car.color)) // ...
instead of
var attribute = typeof(Car) // ...

Mapster - How to do ignore mapping for null properties

I'm using Mapster to map Dto instances to Model objects.
The Dtos are sent by a Javascript client, sending only the properties updated.
I would like to ignore null values, and have Mapster leave the model instance unchanged for this properties.
A simplified example to better explain the scenario:
// My .Net Dto class, used for client/server communication.
public class PersonDto
{
public string Id { get; set; }
public string Name { get; set; }
public string Family { get; set; }
}
// My Model class. Let's assume is the same data as per the Dto.
public class Person
{
public string Id { get; set; }
public string Name { get; set; }
public string Family { get; set; }
}
public void Update()
{
var existingPerson = new Person
{
Id = "A",
Name = "Ned",
Family = "Stark"
};
var patchDataSentFromClient = new PersonDto
{
Id = "A",
Name = "Rob"
};
patchDataSentFromClient.Adapt(existingPerson);
// Here existingPerson.Family should be "Stark", but it gets emptied out.
// the mapping should be equivalent to:
// if (patchDataSentFromClient.Family != null) existingPerson.Family = patchDataSentFromClient.Family;
}
Edit: the point is I don't want to write down the mapping condition for each of the thousands of properties in my Dtos. I want Mapster to Automap all string properties by name, but keep the "patch-like" logic of ignoring null values.
You can use IgnoreNullValues.

AutoMapper: mapping many properties into one

The scenario is the following: I receive a message containing a lot of variables, several hundreds. I need to write this to Azure Table storage where the partition key is the name of the individual variables and the value gets mapped to e.g. Value.
Let’s say the payload looks like the following:
public class Payload
{
public long DeviceId { get; set; }
public string Name { get; set; }
public double Foo { get; set; }
public double Rpm { get; set; }
public double Temp { get; set; }
public string Status { get; set; }
public DateTime Timestamp { get; set; }
}
And my TableEntry like this:
public class Table : TableEntity
{
public Table(string partitionKey, string rowKey)
{
this.PartitionKey = partitionKey;
this.RowKey = rowKey;
}
public Table() {}
public long DeviceId { get; set; }
public string Name { get; set; }
public double Value { get; set; }
public string Signal { get; set; }
public string Status { get; set; }
}
In order to write that to Table storage, I need to
var table = new Table(primaryKey, payload.Timestamp.ToString(TimestampFormat))
{
DeviceId = payload.DeviceId,
Name = payload.Name,
Status = payload.Status,
Value = value (payload.Foo or payload.Rpm or payload.Temp),
Signal = primarykey/Name of variable ("foo" or "rmp" or "temp"),
Timestamp = payload.Timestamp
};
var insertOperation = TableOperation.Insert(table);
await this.cloudTable.ExecuteAsync(insertOperation);
I don’t want to copy this 900 times (or how many variables there happen to be in the payload message; this is a fixed number).
I could make a method to create the table, but I will still have to call this 900 times.
I thought maybe AutoMapper could help out.
Are they always the same variables? A different approach could be to use DynamicTableEntity in which you basically have a TableEntity where you can fill out all additional fields after the RowKey/PartitionKey Duo:
var tableEntity = new DynamicTableEntity();
tableEntity.PartitionKey = "partitionkey";
tableEntity.RowKey = "rowkey";
dynamic json = JsonConvert.DeserializeObject("{bunch:'of',stuff:'here'}");
foreach(var item in json)
{
tableEntity.Properties.Add(item.displayName, item.value);
}
// Save etc
The problem is to map these properties, it is right?
Value = value (payload.Foo or payload.Rpm or payload.Temp),
Signal = primarykey/Name of variable ("foo" or "rmp" or "temp"),
This conditional mapping can be done via Reflection:
object payload = new A { Id = 1 };
object value = TryGetPropertyValue(payload, "Id", "Name"); //returns 1
payload = new B { Name = "foo" };
value = TryGetPropertyValue(payload, "Id", "Name"); //returns "foo"
.
public object TryGetPropertyValue(object obj, params string[] propertyNames)
{
foreach (var name in propertyNames)
{
PropertyInfo propertyInfo = obj.GetType().GetProperty(name);
if (propertyInfo != null) return propertyInfo.GetValue(obj);
}
throw new ArgumentException();
}
You may map rest of properties (which have equal names in source and destination) with AutoMapper.Mapper.DynamicMap call instead of AutoMapper.Mapper.Map to avoid creation of hundreds configuration maps. Or just cast your payload to dynamic and map it manually.
You can create a DynamicTableEntity from your Payload objects with 1-2 lines of code using TableEntity.Flatten method in the SDK or use the ObjectFlattenerRecomposer Nuget package if you are also worried about ICollection type properties. Assign it PK/RK and write the flattened Payload object into the table as a DynamicTableEntity. When you read it back, read it as DynamicTableEntity and you can use TableEntity.ConvertBack method to recreate the original object. Dont even need that intermediate Table class.

Not updating discriminator when property type changes

I've got an entity that has a property that's an abstract type. This creates a one-to-one relationship that uses table-per-hierarchy inheritance. Everything seems like it's working correctly.
I can create an Item and set the Base property to ConcreteOne; everything saves correctly. However, when I try to update Base to ConcreteTwo, EF updates the Base record in the database with the new user value, it doesn't update the discriminator for the type. So the extra data for ConcreteTwo gets persisted, but the discriminator still says ConcreteOne.
The following is a simple example that exposes the problem
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
App_Start.EntityFrameworkProfilerBootstrapper.PreStart();
Database.SetInitializer(new DropCreateDatabaseAlways<DataContext>());
// Create our item with ConcreteOne for Base
using (var context = new DataContext())
{
var item = new Item
{
Base = new ConcreteOne { Name = "Item", Data = 3 }
};
context.Items.Add(item);
context.SaveChanges();
}
// Update Base with a new ConcreteTwo
using (var context = new DataContext())
{
var item = context.Items.FirstOrDefault();
var newBase = new ConcreteTwo()
{
Item = item,
Name = "Item 3",
User = new User { Name = "Foo" }
};
// If I don't set this to null, EF tries to create a new record in the DB which causes a PK exception
item.Base.Item = null;
item.Base = newBase;
// EF doesn't save the discriminator, but DOES save the User reference
context.SaveChanges();
}
// Retrieve the item -- EF thinks Base is still ConcreteOne
using (var context = new DataContext())
{
var item = context.Items.FirstOrDefault();
Console.WriteLine("{0}: {1}", item.Name, item.Base.Name);
}
Console.WriteLine("Done.");
Console.ReadLine();
}
}
public class DataContext : DbContext
{
public DbSet<Item> Items { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Base Base { get; set; }
}
public abstract class Base
{
public int Id { get; set; }
public string Name { get; set; }
[Required]
public virtual Item Item { get; set; }
}
public class ConcreteOne : Base
{
public int Data { get; set; }
}
public class ConcreteTwo : Base
{
public virtual User User { get; set; }
}
}
When the changes are saved, EF generates the following SQL:
update [dbo].[Bases]
set [Name] = 'Item 3' /* #0 */,
[User_Id] = 1 /* #1 */
where (([Id] = 1 /* #2 */)
and [User_Id] is null)
So it's almost correct, but I'd expect to see [Discriminator] = 'ConcreteTwo' in the update statement. Are my expectations unfounded or am I doing something wrong?
As a test, I tried using table-per-type and the the entry was removed from the ConcreteOne table and added to the ConcreteTwo table as I would expect. So it works, but my real application has at least seven sub-types and the SQL statement to retrieve the Base property got really nasty. So I'd certainly like to accomplish this using TPH, if possible.
Update:
I've verified that the problem exists in EF5 as well as EF6.
This question is based on the expectation of an update taking place, which is seemingly a debatable expectation. Currently your best bet if the TPH hierarchy is not functioning as expected, and considering EF6 is currently in beta, is to start a discussion on the Codeplex forums.
Add this to your model:
public enum BaseType
{
ConcreteOne = 1,
ConcreteTwo = 2
}
public abstract class Base
{
...
public BaseType BaseType { get; set; }
...
}
And in the OnModelCreating method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Base>()
.ToTable("Base");
modelBuilder.Entity<ConcreteOne>()
.Map(t => t.Requires(m => m.BaseType).Equals(BaseType.ConcreteOne))
.ToTable("ConcreteOne");
modelBuilder.Entity<ConcreteTwo>()
.Map(t => t.Requires(m => m.BaseType).Equals(BaseType.ConcreteTwo))
.ToTable("ConcreteTwo");
}
I would expect this to create a new instance (record) with a ConcreteTwo discriminator.
using (var context = new DataContext())
{
var item = context.Items.FirstOrDefault();
var newBase = new ConcreteTwo()
{
Name = "Item 3",
User = new User { Name = "Foo" }
};
item.Base = newBase;
context.SaveChanges();
}

Categories