trying to pass generic id's to class constructor - c#

I have below class LibrarySourceTableInput and having structure like as this
public class LibrarySourceTableInput<T> where T: ISourceOfData
{
public LibrarySourceTableInput(List<T> libraries, string mappedLibrarySource)
{
this.LibrarySourceRowInputs = libraries?.Select(l => new LibrarySourceRowInput()
{
LibrarySourceId = l.Id,
SourceOfDataId = l.SourceOfData.Id
}).ToList() ?? new(),
this.MappedLibrarySource = mappedLibrarySource;
}
public List<LibrarySourceRowInput> LibrarySourceRowInputs { get; set; }
public string MappedLibrarySource { get; set; }
}
and then the interface ISourceOfData structure looks like as this below
public interface ISourceOfData : IIdentity
{
public new Guid Id { get; set; }
public CodeStandardGuideline SourceOfData { get; set; }
}
And i am calling above class constructor in other place like as this
var mechanicalData = spaceTypeObject.TargetObject.MechanicalData;
var librarySourceTableInputs = new List<LibrarySourceTableInput<ISourceOfData>>
{
new LibrarySourceTableInput<ISourceOfData>(mechanicalData?.Environments, mappedLibrarySource), // I am getting conversion error here
new LibrarySourceTableInput<ISourceOfData>(mechanicalData?.AirflowsA621 , mappedLibrarySource)
.......
.......
}
and the structure for mechanicaldata.environments is looks like this
public class MechanicalData
{
public List<LibraryEnvironment> Environments { get; set; }
public List<LibraryA621> AirflowsA621 { get; set; }
.......
}
and then last one libraryEnvironment class is looks like this
public class LibraryEnvironment : ISourceOfData
{
public virtual CodeStandardGuideline SourceOfData { get; set; }
.....
......
public Guid Id {get; set;}
}
I have got the below error at this line new LibrarySourceTableInput<ISourceOfData>(mechanicalData?.Environments, mappedLibrarySource)
and the error is Cannot convert from Generic.List<LibraryEnvironment> to Generic.List<IsourceOfData>
Could any one please let me know or any idea on how to send that generic list to that class constructor, Many thanks in advance.

Instead of specifying the interface ISourceOfData (your class already knows the type will have that interface), specify the actual class:
var librarySourceTableInputs = new List<LibrarySourceTableInput<LibraryEnvironment>>
{
new LibrarySourceTableInput<LibraryEnvironment>(mechanicalData?.Environments, mappedLibrarySource),
.......
.......
}

Related

AutoMapper, Mapping a nested collection of collections to List

I have just started using AutoMapper on an asp net core project and I'm trying to map an object that has a collection of an object that also has a collection of an object to an entity.
The source
public class MyClass
{
public List<MyCollection> MyCollections { get; set; }
}
public class MyCollection
{
public int CollectionId { get; set; }
public List<Description> Descriptions { get; set; }
}
public class Description
{
public int DescriptionId { get; set; }
public string Text { get; set; }
}
The destination
public class DescriptionToCollection
{
public int DescriptionId { get; set; }
public int CollectionId { get; set; }
}
I've played around with ConvertUsing thinking something like this, but I can't make it work.
CreateMap<MyClass, List<DescriptionToCollection>>()
.ConvertUsing(source => source.MyCollections.Select(x =>x.Description.Select(y=> new DescriptionToCollection{ DescriptionId=y.DescriptionId,CollectionId=x.CollectionId}).ToList()
));
Searching AutoMappers docs and the internet, I couldn't find anything similar to my problem.
Any help is highly appreciated.
Besides a typo in your example code, you almost had it. Because you aren't mapping 1:1 at the top level, you need to flatten somewhere, and you do that using SelectMany, moving the ToList call appropriately.
CreateMap<MyClass, List<DescriptionToCollection>>()
.ConvertUsing(source => source.MyCollections.SelectMany(x => // SelectMany to flatten
x.Descriptions.Select(y =>
new DescriptionToCollection
{
DescriptionId = y.DescriptionId,
CollectionId = x.CollectionId
}
) // ToList used to be here
).ToList()
);
Try to implement ITypeConverter, follow the example code:
Your Classes
public class Class1
{
public List<Class2> class2 { get; set; }
}
public class Class2
{
public int CollectionId { get; set; }
public List<Class3> class3 { get; set; }
}
public class Class3
{
public int DescriptionId { get; set; }
public string Text { get; set; }
}
public class ClassDto
{
public int DescriptionId { get; set; }
public int CollectionId { get; set; }
}
The custom map
public class ClassCustomMap : ITypeConverter<Class1, List<ClassDto>>
{
public List<ClassDto> Convert(Class1 source, List<ClassDto> destination, ResolutionContext context)
{
var classDtoList = new List<ClassDto>();
foreach (var item in source.class2)
{
var collectionID = item.CollectionId;
foreach (var obj in item.class3)
{
var classDto = new ClassDto();
classDto.CollectionId = collectionID;
classDto.DescriptionId = obj.DescriptionId;
classDtoList.Add(classDto);
}
}
return classDtoList;
}
}
The mapping declaration
CreateMap<Class1, List<ClassDto>>().ConvertUsing<ClassCustomMap>();
How to use it
var class2 = new Class2();
class2.CollectionId = 2;
var class3 = new Class3();
class3.DescriptionId = 1;
class3.Text = "test";
class2.class3 = new System.Collections.Generic.List<Class3>() { class3 };
var class1 = new Class1();
class1.class2 = new System.Collections.Generic.List<Class2>() { class2 };
var result = mapper.Map<List<ClassDto>>(class1);
For clarity and to simplify I have used explicit cycles, if you want you can optimize the conversion function using LinQ and Lambda
You are missing the purpose of auto-mapper.
It should be used for transforming an input object of one type into an output object of a different type.
And you wanted to create a map from MyClass type to List - this should be treated as projection.
You can achieve that using LINQ (for example as a extension method on MyClass):
public static class MyClassExtension
{
public static List<DescriptionToCollection> ToDescriptionToCollection(this MyClass value)
{
return value.MyCollections.SelectMany(mc => mc.Descriptions.Select(d => new DescriptionToCollection()
{
CollectionId = mc.CollectionId,
DescriptionId = d.DescriptionId
})).ToList();
}
}

Get existing instance of List<T>

I've inherited a bloated project that uses a huge class as an in-memory database:
public class Database
{
public class Parameter1
{
public string Code { get; set; }
public string Label { get; set; }
public List<Parameter1Value> paramValues;
}
public class Parameter2
{
public string Code { get; set; }
public string Label { get; set; }
public List<Parameter2Value> paramValues;
}
public class Parameter1Value
{
public string Value { get; set;}
public Parameter parameter { get; set;}
}
public class Parameter2Value
{
public int Value { get; set;}
public Parameter2 parameter { get; set;}
}
public List<Parameter1> parameter1List { get; set; }
public List<Parameter2> parameter2List { get; set; }
}
I am creating a generic method that creates instances of Parameter1 or Parameter2 (see below) and should add those to their respective lists, but I don't know how to use those types to get the parameter1List or parameter2List instances from my Database class. The Database class holds only one List<T> property for each defined type. Is this possible?
This is the generic method used to create instances:
public static Database Add<T>(this Database database, string code, string label) where T : new()
{
T itemToCreate = (T)Activator.CreateInstance(typeof(T));
itemToCreate.Code = code;
itemToCreate.Label = label;
var listForItem = database.GetList<T>; // This is the missing functionality
listForItem.Add(itemToCreate);
return database;
}
Here is a solution using interfaces and generic constraints.
Create an interface to represent a generic parameter class and add members to the interface as required:
public interface IParameter { ... }
And an interface to represent a list of parameters:
public interface IParameterList<TParameter> where TParameter : IParameter
{
List<TParameter> ParameterList { get; set; }
}
Have the Database and Parameter classes implement these new interfaces:
public class Parameter1 : IParameter
public class Parameter2 : IParameter
public class Database : IParameterList<Parameter1>, IParameterList<Parameter2>
{
List<Parameter1> IParameterList<Parameter1>.ParameterList { get => parameter1List; set => parameter1List = value; }
List<Parameter2> IParameterList<Parameter2>.ParameterList { get => parameter2List; set => parameter2List = value; }
...
}
Add a where TParameter : IParameter constraint to your generic Parameter factory function, and have the factory function require an argument of type IParameterList<TParameter> which is an instance of the Database class. This satisfies the compiler that the Database class owns a list of TParameter. Now we just do db.ParameterList.Add(r) to add our new parameter to the correct list.
public static TParameter CreateParameter<TParameter>(IParameterList<TParameter> db) where TParameter : IParameter, new()
{
var r = new TParameter(); // This is the generic function you mentioned. Do stuff here to create your Parameter class.
db.ParameterList.Add(r); // Add the newly created parameter to the correct list
return r;
}
Code dump (full working version after I picked up your edit which added the generic factory function):
public class Parameter1 : IParameter
{
public string Code { get; set; }
public string Label { get; set; }
public List<Parameter1Value> paramValues;
}
public class Parameter2 : IParameter
{
public string Code { get; set; }
public string Label { get; set; }
public List<Parameter2Value> paramValues;
}
public class Parameter1Value
{
public string Value { get; set; }
public Parameter parameter { get; set; }
}
public class Parameter2Value
{
public int Value { get; set; }
public Parameter2 parameter { get; set; }
}
public class Database : IParameterList<Parameter1>, IParameterList<Parameter2>
{
// Note: Setters for the List properties probably not needed here or in IParameterList as with the following code we instantiate them at class construction time and, in this MCVE at least, there are no further assignments
public List<Parameter1> parameter1List { get; set; } = new List<Parameter1>();
public List<Parameter2> parameter2List { get; set; } = new List<Parameter2>();
List<Parameter1> IParameterList<Parameter1>.ParameterList { get => parameter1List; set => parameter1List = value; }
List<Parameter2> IParameterList<Parameter2>.ParameterList { get => parameter2List; set => parameter2List = value; }
public static TParameter Add<TParameter>(IParameterList<TParameter> db, string code, string label) where TParameter : IParameter, new()
{
var itemToCreate = new TParameter();
itemToCreate.Code = code;
itemToCreate.Label = label;
db.ParameterList.Add(itemToCreate); // Add the newly created parameter to the correct list
return itemToCreate;
}
}
public interface IParameter
{
string Code { get; set; }
string Label { get; set; }
}
public interface IParameterList<TParameter> where TParameter : IParameter
{
List<TParameter> ParameterList { get; set; }
}
// Testing:
void Main()
{
var db = new Database();
Database.Add<Parameter1>(db, "hello", "hello2");
Database.Add<Parameter1>(db, "hello", "hello2");
Database.Add<Parameter2>(db, "hello", "hello2");
Console.WriteLine($"P1 count (should be 2): {db.parameter1List.Count()}; P2 count (should be 1): {db.parameter2List.Count}");
}
Output:
P1 count (should be 2): 2; P2 count (should be 1): 1
Here is a solution which acquires the target list using generics and reflection:
public static List<T> GetList<T>(this Database dataBase) where T : new()
{
return dataBase.GetType()
.GetProperties()
.Where(x => x.PropertyType == typeof(List<T>))
.Select(x => (List<T>)x.GetValue(dataBase))
.FirstOrDefault();
}
Credit: Michael Randall in the comments

Converting inherited class with generic class property

IListModel exposes a generic list property called Items of abstract type ListItemModel. But when I try to convert any derived class to IListModel I get "Object reference not set to an instance of an object" error.
public abstract class ListItemModel
{
public string UserName { get; set; }
public string SomeProperty { get; set; }
}
public interface IListModel<T> where T : ListItemModel
{
List<T> Items { get; set; }
}
public class UserListModel : IListModel<UserListItemModel>
{
public string Query { get; set; }
public int TotalUsers { get; set; }
public List<UserListItemModel> Items { get; set; }
}
public class UserListItemModel : ListItemModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
var users = new UserListModel
{
Query = "a",
TotalUsers = 1111,
Items = new List<UserListItemModel>
{
new UserListItemModel {FirstName = "a", LastName = "b"}
}
};
// later in the application users will be passed around as an object which
// must cast it to IListModel<ListItemModel> in order to access its properties
// but converted will return null
var converted = users as IListModel<ListItemModel>;
foreach (var item in converted .Items)
{
item.SomeProperty = DoSomethingHere(item.UserName);
}
What I am trying to achieve here is being able to populate SomePropery from ListItemModel base class.
Please refer to: as (C# reference)
However, if the conversion isn't possible, as returns null instead of raising an exception.
Basically, what's happening is that you're doing an invalid cast, and the result of that invalid cast depends on the form of casting used.
var converted = users as IListModel<ListItemModel>; // converted is null
var converted = (IListModel<ListItemModel>)users; // raises exception
Is casting actually necessary? UserListModel : IListModel<UserListItemModel> seems to indicate that it IS an IListModel of the type you want, so you should just be able to supply it into the foreach block and work from that, no?
Create a generic list item reference interface:
public interface IListItemModel
{
List<ListItemModel> Items { get; }
}
Have your classes implement with an explicit constructor:
List<LIstItemModel> IListModel.Items
{
get { return this.Items; }
}
And then you can cast users to IListModel.
Thanks to "Brian Mains" and "Kyle Baran" my problem is solved. Here is the working code:
public abstract class ListItemModel
{
public string UserName { get; set; }
}
public interface IListModel
{
List<ListItemModel> ListItems { get; }
}
public abstract class BaseListModel<T> : IListModel where T : ListItemModel
{
public List<T> Items { get; set; }
public List<ListItemModel> ListItems
{
get { return Items.Cast<ListItemModel>().ToList(); }
}
}
public class UserListModel : BaseListModel<UserListItemModel>
{
public string Query { get; set; }
public int TotalUsers { get; set; }
}
public class UserListItemModel : ListItemModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
var users = new UserListModel
{
Query = "a",
TotalUsers = 1111,
Items = new List<UserListItemModel>
{
new UserListItemModel {FirstName = "a", LastName = "b"}
}
};
// and finally I can cast the collection to IListModel
var converted = users as IListModel;
foreach (var item in converted .Items)
{
item.SomeProperty = DoSomethingHere(item.UserName);
}

AutoMapper isn't recognizing profile-specific prefixes

I'm trying to use AutoMapper to take data from a class that has prefixes before property names and map it to a second class that doesn't have those prefixes. However, I don't necessarily want it to always strip out that prefix: I just want it to do it for this particular mapping.
My source class looks like this:
public class AdvancedSearchFilterDataModel
{
// ....
public string ServiceMeterNumber { get; set; }
// ....
}
My destination class looks like this:
[DataContract]
public class ServicesAdvancedSearchFilterData : AdvancedSearchFilterData
{
// ....
[DataMember]
public string MeterNumber { get; set; }
// ....
}
When I try to map values like this, it works:
Mapper.Configuration.RecognizePrefixes("Service");
Mapper.CreateMap<AdvancedSearchFilterDataModel, ServicesAdvancedSearchFilterData>();
ServicesAdvancedSearchFilterData servciesFilterData =
Mapper.Map<ServicesAdvancedSearchFilterData>(model);
But I only want "Service" to be recognized as a prefix for certain mappings, since it's also used as a normal part of property names in other mappings. I tried to handle this with a profile, but this didn't work -- no data was mapped:
Mapper.CreateProfile("ServicePrefix").RecognizePrefixes("Service");
Mapper.CreateMap<AdvancedSearchFilterDataModel, ServicesAdvancedSearchFilterData>()
.WithProfile("ServicePrefix");
ServicesAdvancedSearchFilterData servciesFilterData =
Mapper.Map<ServicesAdvancedSearchFilterData>(model);
How can I make it recognize the prefix only when I want it to, either using profiles or some other technique? (I also have other prefixes that I'm going to need it to recognize for other mappings in the same way.)
I achieved this functionality by creating following structure:
I have Person model for my view which is flattened from PersonCombined
public class PersonCombined
{
public Person Person { get; set; }
public Address DefaultAddress { get; set; }
public Contact EmailContact { get; set; }
public Contact PhoneContact { get; set; }
public Contact WebsiteContact { get; set; }
}
public class Person : IWebServiceModel
{
public int ID { get; set; }
public string PersonFirstName { get; set; }
public string PersonSurname { get; set; }
public string PersonDescription { get; set; }
public Nullable<bool> PersonIsActive { get; set; }
}
Then I have separate class for this mapping only that looks like this:
public class PersonCustomMapping : ICustomMapping
{
const string separator = " ";
private static IMappingEngine _MappingEngine;
public IMappingEngine MappingEngine
{
get
{
if (_MappingEngine == null)
{
var configuration = new ConfigurationStore(new TypeMapFactory(), AutoMapper.Mappers.MapperRegistry.Mappers);
configuration.RecognizePrefixes("Person");
configuration.RecognizeDestinationPrefixes("Person");
configuration.CreateMap<Person, MCIACRM.Model.Combine.PersonCombined>();
configuration.CreateMap<MCIACRM.Model.Combine.PersonCombined, Person>();
_MappingEngine = new MappingEngine(configuration);
}
return _MappingEngine;
}
}
}
In my generic view I have mappingEngine property like this:
private IMappingEngine mappingEngine
{
get
{
if (_mappingEngine == null)
{
_mappingEngine = AutoMapper.Mapper.Engine;
}
return _mappingEngine;
}
}
Finally in my generic view constructor i have:
public GenericEntityController(IGenericLogic<S> logic, ICustomMapping customMapping)
: base()
{
this._mappingEngine = customMapping.MappingEngine;
this.logic = logic;
}
And that's how I do mapping:
result = items.Project(mappingEngine).To<R>();
or
logic.Update(mappingEngine.Map<S>(wsItem));
Because I use 1 entity per view I can define custom mapping configuration per entity.
Hope this helps

Make generic object instantiation more generic

I've got this piece of code to create new objects in a generic way:
var user = User.Create<User>(c => c.Name = "321X");
What I don't like about it is the fact I need to pass the 'generic notation' <T> for every create call. After all I create an object that I'm already referring to...
The code behind this current functionality is:
public class User : CreateBase
{
public string Name { get; set; }
}
public abstract class CreateBase
{
public DateTime CreateDate { get; set; }
public Guid Guid { get; set; }
public static T Create<T>(Action<T> init) where T : CreateBase, new()
{
T obj = new T();
obj.Guid = Guid.NewGuid();
obj.DateTime = DateTime.Now;
init(obj);
return obj;
}
}
Is it possible (and how) to refactor my code to this, to create an object?
var user = User.Create(c => c.Name = "321X");
Thanks!
Define the generic argument on the class level:
public abstract class CreateBase<T> where T : CreateBase<T> , new()
{
public static T Create(Action<T> init)
{
//...
}
}
public class User : CreateBase<User>
{
public string Name { get; set; }
}
Then you can write var user = User.Create(c => c.Name = "321X");
Otherwise the compiler cannot infer the type for your Create method without specifying the type argument.
You were not very far. Try this modification:
public abstract class CreateBase<T> where T : CreateBase<T> , new()
{
public DateTime CreateDate { get; set; }
public Guid Guid { get; set; }
public static T Create(Action<T> init)
{
T obj = new T();
obj.Guid = Guid.NewGuid();
obj.CreateDate = DateTime.Now;
init(obj);
return obj;
}
}
public class User : CreateBase<User>
{
public string Name { get; set; }
}
EDIT: Updated the code after I tested it on my local environment. It works now.
You are doing it the wrong way. Instead of getting rid of the generic argument, get rid of (needlessly) saying User.. Instead:
CreateBase.Create<User>(...)
No more redundancies.
Besides that, calling a static member of the base class through a derived class is an anti-pattern.
A better approach would be to include this functionality in the constructor of the base class (I call it ModelBase)
public abstract class ModelBase
{
public DateTime CreateDate { get; private set; }
public Guid Guid { get; private set; }
public ModelBase()
{
Guid = Guid.NewGuid();
DateTime = DateTime.Now;
}
}
public User : ModelBase
{
public User()
: base()
{
}
public User(string name)
: base()
{
Name = name
}
public string Name { get; set; }
}
Creating a user the standard way will initialize the Guid and date automatically
var user = new User { Name = "xy };
EDIT
I added a second constructor with a name parameter. I you want to force the initialization of the name, drop the first parameterless constructor.
var user = new User("xy");
If you really uncomfortable with that sintax (I, honestly, don't see much problem here)
you can do the following:
public class User : CreateBase
{
public string Name { get; set; }
public static User Create(Action<User> a)
{
return Create<User>(a); //CALL BASE CLASS GENERIC FUNCTION
}
}
After you can call it in a way you would like to do that :
var user = User.Create(c => c.Name = "321X");

Categories

Resources