Automapper and complex object - c#

I have a complex object that I need to translate on a List of simple DTO object.
My map is this:
CreateMap<ObjASource, IEnumerable<MyDto>>()
.ConvertUsing(source => source.ObjB?.ObjC?.Select(p => new MyDto
{
field1 = source.field1,
field2 = source.field2,
field3 = source.field3,
field4 = p.fieldX,
field5 = p.fieldY
}).ToList()
);
This map works good when I'm working with singol ObjA
like thismapper.Map<IList<MyDto>>(my_singol_objA); but doesn't work when I'm working with a list of ObjA mapper.Map<IList<MyDto>>(my_list_of_objA);
I don't know which other kind of map I have to add to correct this.
Thanks guys
EDITED because I didn't solve
I want to explain better and in more simple way my problems, maybe someone can help me.
I have a complex object with inside a Collection.
An object like this:
public class Product {
int product_id {get; set;};
string name {get; set;};
List<CompaniesProvideProduct> company {get; }
}
The CompaniesProvideProduct contains the relation with a company that sell the product and have more details.
public class CompaniesProvideProduct {
int product_id {get; set;};
int company_id {get; set;};
decimal price {get; set;};
}
Then the destination object of my translation is:
public class ProductDto {
int product_id {get; set;};
string name {get; set;};
int company_id {get; set;};
decimal price {get; set;};
}
In my program I get from db a List and I want to translate this object in a List. For 1 record of Product I'll have more record of ProductDto.
I tried with:
CreateMap<Product, ProductDto>(); //to get product_id and name
CreateMap<CompaniesProvideProduct, ProductDto>(); //to get company_id and price
CreateMap<Product, IEnumerable<ProductDto>>()
.ConvertUsing<ProductConverter>();
public class ProductConverter : ITypeConverter<Product, IEnumerable<ProductDto>>
{
IEnumerable<ProductDto> ITypeConverter<Product, IEnumerable<ProductDto>>.Convert(Product source, IEnumerable<ProductDto> destination, ResolutionContext context)
{
Product prod = source;
foreach (var dto in prod.company.Select(e => context.Mapper.Map<ProductDto>(e)))
{
context.Mapper.Map(prod, dto);
yield return dto;
}
}
}
But this doesn't work for me. If someone can help me I'll be very happy.
Thanks.

#Lucian is correct. Collections are handled by default. So if your original mapping is setup correctly object => list<object> then list<object> => list<list<object>> would work as well.
expression.CreateMap<ObjASource, List<MyDto>>()
.ConvertUsing(source =>
{
return source.ObjB?.ObjC?.Select(p => new MyDto
{
field1 = source.field1,
field2 = source.field2,
field3 = source.field3,
field4 = p.fieldX,
field5 = p.fieldY
})
.ToList();
}
);
Here's the working sample:
var test = new List<ObjASource>
{
new ObjASource
{
field1 = "1",
field2 = "2",
field3 = "3",
ObjB = new ObjB
{
ObjC = new List<ObjC>
{
new ObjC
{
fieldX = "X",
fieldY = "Y"
}
}
}
}
};
var result = Mapper.Map<List<List<MyDto>>>(test);
UPDATE
So based on your updated question I think I better understand your issue. Basically you need 2 explicit mappings - object => List<object> AND List<object> => List<object> - because as explained earlier AutoMapper gives List of List of the object which you do not want:
var mapper = new MapperConfiguration(exp =>
{
exp.CreateMap<List<Product>, List<ProductDto>>()
.ConstructUsing(products => products.SelectMany(product => product.company,
(product, companiesProvideProduct) => new ProductDto
{
product_id = product.product_id,
name = product.name,
company_id = companiesProvideProduct.company_id,
price = companiesProvideProduct.price
}).ToList());
exp.CreateMap<Product, List<ProductDto>>()
.ConvertUsing(product => product.company.Select(provideProduct => new ProductDto
{
product_id = product.product_id,
name = product.name,
company_id = provideProduct.company_id,
price = provideProduct.price
}).ToList());
}).CreateMapper();
So then if your object initialization looked like the following:
var productList = new List<Product>
{
new Product
{
product_id = 1,
name = "test1",
company = new List<CompaniesProvideProduct>
{
new CompaniesProvideProduct
{
company_id = 1,
price = 1.99m,
product_id = 1
}
}
},
new Product
{
product_id = 2,
name = "test2",
company = new List<CompaniesProvideProduct>
{
new CompaniesProvideProduct
{
company_id = 2,
price = 1.99m,
product_id = 2
}
}
}
};
The mapped objects would be:
var result1 = mapper.Map<List<ProductDto>>(productList);
var result2 = mapper.Map<List<ProductDto>>(productList.First());

Related

Create object containing a list using Dynamic Linq Core with Entity Framework 2.0

I have the following two classes:
public Part {
public string PartNumber {get; set;}
public string Description {get; set;}
public List<Warehouse> Warehouses {get; set;}
}
public Warehouse {
public string PartNumber {get; set;}
public string WarehouseName {get; set;}
public int Quantity {get; set;}
public int ReorderPoint {get; set;}
}
Using Entity Framework Core 2.0 I have associated these using a one to many relationship. Using Dynamic Linq Core I'm trying to create a query that returns the PartNumber, Description, and the list of all associated Warehouses for a particular part where the only property in the Warehouses list is WarehouseName ideally like this:
List<string> fields = new List<string> {"PartNumber", "Description", "Warehouses.WarehouseName"};
var _dataSet = dbContext.Parts.Include(x => x.Warehouses);
var data = _dataSet.Where("PartNumber = \"Part1234\"").Select("new (" + String.Join(",", fields) + ")").ToDynamicArray();
But I receive this error: "No property or field 'Warehouse' exists in type 'List`1'". If I do something like this it works fine:
var data = _dataSet.Where("PartNumber = \"Part1234\"").Select(x => new Part
{
PartNumber = x.PartNumber,
Description = x.Description,
Warehouses = x.Warehouses.Select(y => new Warehouse { Warehouse = y.Warehouse }).ToList()
}).Single();
The problem is that I would like it to be dynamic so that the user can just pass in a list of fields from the Part and Warehouse class that they want to get without having to modify the select to build it for those specific fields.
You'd need to support a subquery on Warehouses. I'll copy the relevant steps listed in this answer:
add the following in ParseAggregate:
Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos)
{
// Change starts here
var originalIt = it;
var originalOuterIt = outerIt;
// Change ends here
outerIt = it;
ParameterExpression innerIt = Expression.Parameter(elementType, elementType.Name);
it = innerIt;
Expression[] args = ParseArgumentList();
// Change starts here
it = originalIt;
outerIt = originalOuterIt;
// Change ends here
...
}
Add Select and ToList into IEnumerableSignatures, and a respective condition in ParseAggregate:
interface IEnumerableSignatures
{
...
void Select(object selector);
void ToList();
...
}
Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos)
{
...
if (signature.Name == "Min" ||
signature.Name == "Max" ||
signature.Name == "Select")
...
}
Finally, Your query would be:
static void Main()
{
// example data
var warehouses = new List<Warehouse>
{
new Warehouse { WarehouseName = "NY1", Quantity = 10 },
new Warehouse { WarehouseName = "NY2", Quantity = 100 }
};
var parts = new List<Part>
{
new Part { PartNumber = "1", Description = "Hammer", Warehouses = warehouses }
};
// query
var result =
parts
.Select(#"new (
PartNumber,
Description,
Warehouses.Select(WarehouseName).ToList() as WarehouseNames
)");
}

How to query and sort a complex type consisting of IEnumerable<T> using LINQ?

Trying to figure out how to query an IEnumerable<T> using LINQ. The following simple example without IEnumerable works fine:
class Category
{
public string Title { get; set; }
public NameValue SubCategory { get; set; }
}
class NameValue
{
public string Name { get; set; }
public string Value { get; set; }
}
private static void testLinq()
{
Category[] categories = {
new Category { Title ="Abc", SubCategory = new NameValue { Name = "A", Value = "5"} },
new Category { Title ="Xyz", SubCategory = new NameValue { Name = "B", Value = "10" } }
};
IEnumerable<Category> q = categories.OrderBy(c => c.Title).ThenBy(c => c.SubCategory.Name);
foreach (Category c in q)
{
Console.WriteLine("{0} - {1}", c.Title, c.SubCategory.Name);
}
}
When I change the signature to have an IENumerable<NameValue> instead then I cannot access c.SubCategory.Name:
class Category
{
public string Title { get; set; }
public IEnumerable<NameValue> SubCategory { get; set; }
}
// For example, below does not compile:
IEnumerable<Category> q = categories.OrderBy(c => c.Title).ThenBy(c => c.SubCategory.Name);
// Also, this initialization of course won't work either
Category[] categories = {
new Category { Title ="Abc", SubCategory = new NameValue { Name = "A", Value = "5"} },
new Category { Title ="Xyz", SubCategory = new NameValue { Name = "B", Value = "10" } }
};
The error is:
IEnumerable' does not contain a definition for 'Name' and no extension method 'Name' accepting a first argument of type 'IEnumerable' could be found (are you missing a using directive or an assembly reference?)
Do I need to do a cast of some sort?
Update:
Output should be something like:
Abc (category)
A (sub)
B (sub)
C (...)
Xyz
B
K
M
Xyz2
A
Q
Z
In SQL I would do like something like this:
SELECT c.Title, s.Name, s.Value FROM Categories c
INNER JOIN SubCategory s ON
c.CategoryID = s.CategoryID
ORDER BY c.Title, s.Name -- sorting first on Category.Title, then on SubCategory.Name
Your SubCategory will be a collection so you cannot do it using ThenBy. You need to order the Category(s) and then order their SubCategory(s) like this. Note I added two SubCategory(s) to the first Category but their order is not correct. After we order them, then they will be correct:
Category[] categories = {
new Category { Title ="Abc", SubCategory = new List<NameValue>
{ new NameValue { Name = "B", Value = "5"},
new NameValue { Name = "A", Value = "5"} } },
new Category { Title ="Xyz", SubCategory = new List<NameValue>
{ new NameValue { Name = "A", Value = "10" } } }};
// First order by categories
var cats = categories.OrderBy(c => c.Title)
// Then create a new category and order by sub categories
.Select(x => new Category { Title = x.Title,
SubCategory = x.SubCategory.OrderBy(y => y.Name) });
If you can get away with only sorting the children when you need to use them, sorting by the parent and then sorting the children upon use like this would be fairly efficient:
public void DisplayA(A value)
{
Console.WriteLine(value.Name);
foreach (var child in value.Children.OrderBy(c => c.Name))
{
Console.WriteLine(string.Format("- {0}", child.Name));
}
}
Or if you want to avoid that, you could add a sorted property to the class. Since it's Linq, it will only be evaluated when you iterate through the list.
public class A
{
public string Name { get; set; }
public IEnumerable<B> Children { get; set; }
public IEnumerable<B> SortedChildren { get { return Children.OrderBy(ca => ca.Name); } }
}
public class B
{
public string Name { get; set; }
}
If they don't work for you, you could try these, but they won't be so efficient since you're creating new objects.
// This will flatten it into a single object, sorted by one field and the the other. Since this is Linq, it will create these new flattened objects each time you iterate through the IEnumerable.
public IEnumerable<FlattenedA> GetSortedFlattened(IEnumerable<A> collection)
{
var flattened = collection.SelectMany(a => a.Children.Select(ca => new FlattenedA() { Name = a.Name, SubName = ca.Name }));
var sorted = flattened.OrderBy(f => f.Name).ThenBy(f => f.SubName);
return sorted;
}
// This will return objects of A, where the child enumerable has been replaced with an OrderBy. Again this will return new objects each time you iterate through. Only when you iterate through the children will they be sorted.
public IEnumerable<A> GetSortedNonFlattened(IEnumerable<A> collection)
{
var withSortedChildren = collection.Select(a => new A() { Name = a.Name, Children = a.Children.OrderBy(ca => ca.Name) });
var sorted = withSortedChildren.OrderBy(a => a.Name);
return sorted;
}
public class FlattenedA
{
public string Name { get;set }
public string SubName { get; set; }
}
public class A
{
public string Name { get; set; }
public IEnumerable<B> Children { get; set; }
}
when you are setting it as IEnumerable you can't do this
SubCategory = new NameValue { Name = "A", Value = "5"}
you should use some implementation of IEnumerable,
like List<>
so it should be something like this
SubCategory = new List<NameValue>{new NameValue { Name = "A", Value = "5"}, addmore here};
and for your order linq, i would do this,
var OrderedCategories = categories.select(g =>
new Category{ Name = g.Name, subcategories = g.subcategories.orderby(h => h.Name) });
That's because your SubCategory now is no longer a simple single instance of NameValue, but rather an enumeration of those. So now you need to specify how to .ThenBy over a collection of .Names.

Reusable linq select query in Entity Framework

In my application multiple reports are needed on some table, many of the fields are common in most reports, as a sample:
public class ReportStudent
{
public int Id {get; set;}
public string Name {get; set;}
public string Family {get; set;}
public DateTime BirthDate {get; set;}
public DateTime RegisterDate {get; set;}
public Double Average {get; set;}
public string FatherName {get; set;}
public string MotherName {get; set;}
}
var list1 = context.Students.Select(e=> new ReportStudent
{
Id = e.Id
Name = e.Name
Family = e.Family
BirthDate = e.BirthDate
RegisterDate = e.RegisterDate
FatherName = e.FatherName
MotherName = e.MotherName
}).ToList();
var list2 = context.Students.Select(e=> new ReportStudent
{
Id = e.Id
Name = e.Name
Family = e.Family
BirthDate = e.BirthDate
RegisterDate = e.RegisterDate
Average = e.Average
}).ToList();
How can I write this map only once? These fields are common in list1 and list2.
Id = e.Id
Name = e.Name
Family = e.Family
BirthDate = e.BirthDate
RegisterDate = e.RegisterDate
First, define an expression that will contain your common projection needs:
Expression<Func<ReportStudent, ReportStudent>> commonProjection = e => new ReportStudent
{
Id = e.Id,
Name = e.Name,
Family = e.Family,
BirthDate = e.BirthDate,
RegisterDate = e.RegisterDate,
};
Then have a method that will modify this expression to reflect the additional bindings:
public static Expression<Func<ReportStudent, ReportStudent>> MergeBindings(Expression<Func<ReportStudent, ReportStudent>> expr, Expression<Func<ReportStudent, ReportStudent>> newExpr)
{
var reportStudentType = typeof(ReportStudent);
var eParameter = expr.Parameters.First();
var eNew = Expression.New(reportStudentType);
var memberInitExpr = expr.Body as MemberInitExpression;
var memberInitNewExpr = newExpr.Body as MemberInitExpression;
var allBindings = memberInitExpr.Bindings.Concat(memberInitNewExpr.Bindings.Select(x =>
Expression.Bind(x.Member, Expression.Property(eParameter, x.Member as PropertyInfo)
)));
var eInit = Expression.MemberInit(eNew, allBindings);
var lambda = Expression.Lambda<Func<ReportStudent, ReportStudent>>(eInit, eParameter);
return lambda;
}
Usage:
var withParentsExpr = MergeBindings(commonProjection, e => new ReportStudent
{
FatherName = e.FatherName,
MotherName = e.MotherName
});
var list1 = context.Students.Select(withParentsExpr).ToList();
var withAverageExpr = MergeBindings(commonProjection, e => new ReportStudent
{
Average = e.Average
});
var list2 = context.Students.Select(withAverageExpr).ToList();
(With some help from #Nicholas Butler great answer)
If you don't want to write maps every time, you can use great library http://automapper.org/
With this library, you can define map and it automatically map all properties
You could create a function for that let say you have
public StudentReport ParseStudentReport(Student e)
{
return new StutentReport{
Id = e.Id
Name = e.Name
Family = e.Family
BirthDate = e.BirthDate
RegisterDate = e.RegisterDate
}
}
Then use it within your select statement
var list2 = context.Students.Select(ParseStudentReport);
Then add remaining properties or you could use AutoMapper, which can be found on github or as a nuget package.

Saving a list of parent objects and their children (that have foreign keys of parent) all at the same time with EF

Will the below code automatically save the Sales Orders to the SalesOrder table and the Sales Order Details to the SalesOrderDetail table?
My goal is to be able to create a list of my primary entitity and use it's navigation property to create another list and save it all in one shot.
Please see below:
List<SalesOrder> salesOrders = new List<SalesOrder>()
{
new SalesOrder()
{
OrderDate = DateTime.Now,
OrderDetails = new List<SalesOrderDetail>()
{
new SalesOrderDetail() { ItemId = 1 },
new SalesOrderDetail() { ItemId = 2 },
new SalesOrderDetail() { ItemId = 3 }
}
},
new SalesOrder()
{
OrderDate = DateTime.Now,
OrderDetails = new List<SalesOrderDetail>()
{
new SalesOrderDetail() { ItemId = 1 },
new SalesOrderDetail() { ItemId = 2 }
}
}
};
_context.SalesOrders.AddRange(salesOrders);
_context.SaveChanges();
or am I going to have to do something like:
_context.SalesOrders.AddRange(salesOrders);
_context.SalesOrderDetails.AddRange(salesOrders[0].OrderDetails);
_context.SalesOrderDetails.AddRange(salesOrders[1].OrderDetails);
_context.SaveChanges();
I am using Entity Framework Core 1.0 (EF7) and the following worked perfectly. Please take note at how my models are setup:
public class SalesOrder
{
public int Id {get; set;}
public DateTime OrderDate {get; set;}
// Nav property
public ICollection<SalesOrderDetail> OrderDetails { get; set; }
}
public class SalesOrderDetail
{
public int Id {get; set;}
// Foreign key property
public int SalesOrderId {get; set;}
// Nav property
public SalesOrder SalesOrder {get; set;}
}
Simply doing the following works and automatically saves the correct foreign key values without me having to specify them:
List<SalesOrder> salesOrders = new List<SalesOrder>()
{
new SalesOrder()
{
OrderDate = DateTime.Now,
OrderDetails = new List<SalesOrderDetail>()
{
new SalesOrderDetail() { ItemId = 1 },
new SalesOrderDetail() { ItemId = 2 },
new SalesOrderDetail() { ItemId = 3 }
}
},
new SalesOrder()
{
OrderDate = DateTime.Now,
OrderDetails = new List<SalesOrderDetail>()
{
new SalesOrderDetail() { ItemId = 1 },
new SalesOrderDetail() { ItemId = 2 }
}
}
};
_context.SalesOrders.AddRange(salesOrders);
_context.SaveChanges();
Yes, navigation properties are not only helpful when using Include as you retrieve data, but also make saving a lot easier!

Sequence contains more than one element error in EF CF Migrations

I created some models, added the migration and then did an update database operation, though at my last update database operation I got the error message saying:
Sequence contains more than one element
Below you can find my migration configuration:
context.Categories.AddOrUpdate(p => p.CategoryName,
new Category
{
CategoryName = "Sport"
},
new Category
{
CategoryName = "Music"
}
);
context.Subcategories.AddOrUpdate(p => p.SubcategoryName,
new Subcategory
{
SubcategoryName = "Football"
},
new Subcategory
{
SubcategoryName = "Basketball"
},
new Subcategory
{
SubcategoryName = "Piano"
},
new Subcategory
{
SubcategoryName = "Violin"
}
);
context.Services.AddOrUpdate(p => p.ServiceType,
new Service
{
ServiceType = "Football player",
Category = { CategoryName = "Sport" },
Subcategory = { SubcategoryName = "Football" }
},
new Service
{
ServiceType = "Piano lessons",
Category = { CategoryName = "Music" },
Subcategory = { SubcategoryName = "Piano" }
}
);
The problem occurs with when I add new Services. I already have categories and subcategories, and if I do like Category = new Category { CategoryName = "Music" } then it works but I get Music entry twice in my database (for this example). I want to use the already added categories and subcategories. Below also you can find my models definitions.
public class Category
{
[Key]
public int CategoryID { get; set; }
public string CategoryName { get; set; }
}
// Subcategory is defined the same way...
public class Service
{
public int ServiceID { get; set; }
public string ServiceType { get; set; }
public virtual Category Category { get; set; }
public virtual Subcategory Subcategory { get; set; }
}
Any idea how to solve it?
Sequence contains more than one element
This exception was caused when trying to use AddOrUpdate with specifying identifier expression, like p => p.CategoryName. There might be two Categories that have the same name "Sport" or "Music".
This might also happen on Subcategories and Services, Subcategories uses p => p.SubcategoryName and Services uses p => p.ServiceType.
You need to clean the duplicate entry first on database. This is a must, since you want to use AddOrUpdate, internally this code will use SingleOrDefault and if there is found more than one match element, exception will be thrown.
Object reference not set to an instance of an object
This error is probably caused by this code.
Category = { CategoryName = "Sport" },
Subcategory = { SubcategoryName = "Football" }
Creating new Service will have null Category by default, you can't just directly set the CategoryName.
The problem occurs with when I add new Services. I want to use the already added categories and subcategories.
The Categories and Subcategories should be stored on variables so it can be used by Service.
var sportCategory = new Category { CategoryName = "Sport" };
var musicCategory = new Category { CategoryName = "Music" };
context.Categories.AddOrUpdate(p => p.CategoryName,
sportCategory, musicCategory);
var footballSub = new Subcategory { SubcategoryName = "Football" };
var basketballSub = new Subcategory { SubcategoryName = "Basketball" };
var pianoSub = new Subcategory { SubcategoryName = "Piano" };
var violinSub = new Subcategory { SubcategoryName = "Violin" };
context.Subcategories.AddOrUpdate(p => p.SubcategoryName,
footbalSub, basketballSub , pianoSub, violinSub);
context.Services.AddOrUpdate(p => p.ServiceType,
new Service
{
ServiceType = "Football player",
Category = sportCategory,
Subcategory = footballSub
},
new Service
{
ServiceType = "Piano lessons",
Category = musicCategory,
Subcategory = pianoSub
}
);
But above code still has problem, if service is new entity, the existing sport category and the existing football subcategory will also be added. You can read this article for further explanation.
Solution
You need to also have foreign key values on Service not only foreign key references to prevent adding existing category and existing subcategory.
[ForeignKey("Category")]
public int CategoryId { get; set; }
[ForeignKey("Subcategory")]
public int SubcategoryId { get; set; }
Then run the migration.
Add-Migration AddServiceFKValues
And assign temporary primary key manually and use it when defining service. This temporary primary keys are not needed when dealing with existing entities, but there might be another problem if there is more than one new category or subcategory. To prevent that, it's better to use temporary primary keys, after calling AddOrUpdate, each entity primary keys will be updated if they are existing entities.
var sportCategory = new Category { CategoryID = 1000, CategoryName = "Sport" };
var musicCategory = new Category { CategoryID = 1001, CategoryName = "Music" };
context.Categories.AddOrUpdate(p => p.CategoryName,
sportCategory, musicCategory);
var footballSub = new Subcategory { SubcategoryID = 1000, SubcategoryName = "Football" };
var basketballSub = new Subcategory { SubcategoryID = 1001, SubcategoryName = "Basketball" };
var pianoSub = new Subcategory { SubcategoryID = 1002, SubcategoryName = "Piano" };
var violinSub = new Subcategory { SubcategoryID = 1003, SubcategoryName = "Violin" };
context.Subcategories.AddOrUpdate(p => p.SubcategoryName,
footbalSub, basketballSub , pianoSub, violinSub);
context.Services.AddOrUpdate(p => p.ServiceType,
new Service
{
ServiceType = "Football player",
CategoryID = sportCategory.CategoryID,
SubcategoryID = footballSub.SubcategoryID
},
new Service
{
ServiceType = "Piano lessons",
CategoryID = musicCategory.CategoryID,
SubcategoryID = pianoSub.SubcategoryID
}
);
Then update the database.
Update-Database

Categories

Resources