I need some help to map an anonymous object using Automapper. The goal is combine Product and Unity in a ProductDto (in which unity is a product's property).
Autommaper CreateMissingTypeMaps configuration is set to true.
My Classes:
public class Product
{
public int Id { get; set; }
}
public class Unity
{
public int Id { get; set; }
}
public class ProductDto
{
public int Id { get; set; }
public UnityDto Unity{ get; set; }
}
public class UnityDto
{
public int Id { get; set; }
}
Test Code
Product p = new Product() { Id = 1 };
Unity u = new Unity() { Id = 999 };
var a = new { Product = p, Unity = u };
var t1 = Mapper.Map<ProductDto>(a.Product);
var t2 = Mapper.Map<UnityDto>(a.Unity);
var t3 = Mapper.Map<ProductDto>(a);
Console.WriteLine(string.Format("ProductId: {0}", t1.Id)); // Print 1
Console.WriteLine(string.Format("UnityId: {0}", t2.Id)); // Print 999
Console.WriteLine(string.Format("Anonymous ProductId: {0}", t3.Id)); // Print 0 <<< ERROR: It should be 1 >>>
Console.WriteLine(string.Format("Anonymous UnityId: {0}", t3.Unity.Id)); // Print 999
There are two maps added to the profile:
CreateMap<Product, ProductDto>();
CreateMap<Unity, UnityDto>();
The problem is how Automapper map anonymous objects. I haven't time to check out Automapper source code but I got the desired behaviour with minor changes on my anonymous object:
var a = new { Id = p.Id, Unity = u };
By doing this, I might even delete previous mappings because now it is using only CreateMissingTypeMaps.
Note: As matter of fact I'm not sure if it is really an issue or I it was just my unreal expectations.
Related
I have 3 types :
type [<CLIMutable>] status = { id : Guid; name : string }
type [<CLIMutable>] container = { id : Guid; name : string; status : status}
type [<CLIMutable>] scontainer = { id : Guid; name : string; status : string}
and next configuration
let c =
MapperConfiguration(
fun config ->
config.CreateMap<container, scontainer>()
.ForMemberFs((fun sc -> sc.name), (fun opts -> opts.MapFrom(fun m _ -> m.status.name))) |> ignore
)
I'm trying to map with next code
let con = { id = Guid.NewGuid(); name = "Template 1"; container.status = { id = Guid.NewGuid(); name = "Draft" } }
let mapper = c.CreateMapper()
let sc = mapper.Map<scontainer>(con)
But member mapping isn't called and sc.status contains a string representation of status object(id and name together).
When I add new map:
config.CreateMap<status, string>().ConvertUsing(fun s -> s.name) |> ignore
then sc.status contains correct value.
Does anyone know how to make it work without additional mappings?
Next lines solves my problem:
let c =
AutoMapper.MapperConfiguration(
fun config ->
config.ShouldMapProperty <- fun _ -> false
config.ShouldMapField <- fun _ -> true
)
Metadata produced for constructor is (id, name, status) in both scontainer and container.
Metadata produced for properties is: id, name, status in both scontainer and container.
Metadata produced for fields is: id#, name#, status# in both scontainer and container.
If I don't disable properties usage then automapper will find a match between constructor parameters and properties and will use properties as resolver, which means constructor resolver will be used.
If I disable properties usage, then there will be no match between constructor parameters and fields and constructor will no be used.
I might be wrong but bug is in the next few lines in method: private void MapDestinationCtorToSource(TypeMap typeMap, List ctorParamConfigurations)
in next code:
var resolvers = new LinkedList();
var canResolve = typeMap.Profile.MapDestinationPropertyToSource(typeMap.SourceTypeDetails, destCtor.DeclaringType, parameter.GetType(), parameter.Name, resolvers, IsReverseMap);
if ((!canResolve && parameter.IsOptional) || IsConfigured(parameter))
{
canResolve = true;
}
Here is a test that reproduces a bug
using AutoMapper;
using System;
using Xunit;
namespace ConstructorBug
{
public class Status
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Container
{
public Guid Id { get; set; }
public string Name { get; set; }
public Status Status { get; set; }
}
public class SContainer
{
public SContainer()
{
}
public SContainer(string id, string name, string status)
{
Id = id;
Name = name;
Status = status;
}
public string Id { get; set; }
public string Name { get; set; }
public string Status { get; set; }
}
public class ConstructorBug
{
[Fact]
public void ConstructorMapBug()
{
var mapperConfiguration = new MapperConfiguration(
config =>
{
config.CreateMap<Container, SContainer>()
.ForMember(dest => dest.Status, opts => opts.MapFrom(x => x.Status.Name));
}
);
var mapper = mapperConfiguration.CreateMapper();
var con = new Container
{
Id = Guid.NewGuid(),
Name = "Container 1",
Status = new Status { Id = Guid.NewGuid(), Name = "Draft" }
};
var scon = mapper.Map<SContainer>(con);
Assert.Equal(scon.Id, con.Id.ToString());
Assert.Equal(scon.Name, con.Name);
Assert.Equal(scon.Status, con.Status.Name);
}
}
}
I'm working in Entity Framework 5 and having problems creating an expression to use inside a method.
I believe the problem is that normally I would call the expression in a lambda expression such as dbContext.Counties.Select(GetLargeCities()), but in the code I am working with, I am projecting the Counties entity into a view model called CountyWithCities. Where I would normally call the expression, I have a singleton c and don't know how to call the expression there.
The reason I want to accomplish this using an expression is because I want the GetCountiesWithCities method to hit the database once, with Entity Framework constructing a complex graph for all the objects in the result.
For some reason the code below is producing the error `The name 'GetLargeCities' does not exist in the current context."
public class CountyWithCities // this is a view model
{
public int CountyID { get; set; }
public string CountyName { get; set; }
public IEnumerable<City> Cities { get; set; }
}
public class City // this is an entity
{
public int CityID { get; set; }
public string CityName { get; set; }
public int Population { get; set; }
}
public IEnumerable<CountyWithCities> GetCountiesWithCities(int StateID)
{
return dbContext.States
.Where(s => s.StateID = StateID)
.Select(s => s.Counties)
.Select(c => new CountyWithCities
{
CountyID = c.CountyID,
CountyName = c.CountyName,
Cities = GetLargeCities(c) // How do I call the expression here?
});
}
public Expression<Func<County, IEnumerable<City>>> GetLargeCities = county =>
county.Cities.Where(city => city.Population > 50000);
Thanks!
I normally do this with an extension method.
public static IQueriable<City> LargeCities(this IQueriable<County> counties){
return counties
.SelectMany(county=>county.Cities.Where(c=>c.Population > 50000));
}
usage:
dbContext.Counties.LargeCities()
public IEnumerable<CountyWithCities> GetCountiesWithCities(int StateID)
{
return dbContext.States
.Where(s => s.StateID = StateID)
.Select(s => s.Counties)
.LargeCities()
.GroupBy(c=>c.County)
.Select(c => new CountyWithCities
{
CountyID = g.Key.CountyID,
CountyName = g.Key.CountyName,
Cities = g.AsQueriable() // i cant remember the exact syntax here but you need the list from the group
});
}
Am Using EntityFramework codefirst approach.my coding is
class Blog
{
[Key]
public int BlobId { get; set; }
public string Name { get; set; }
public virtual List<Post> Posts { get; set; }
}
class Post
{
[Key]
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlobId { get; set; }
public virtual Blog Blob { get; set; }
}
class BlogContext:DbContext
{
public BlogContext() : base("constr") { }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
class Program
{
static void Main(string[] args)
{
using (var db = new BlogContext())
{
Console.WriteLine("Enter a name for a new blob:");
var name = Console.ReadLine();
var b = new Blog { Name = name };
db.Blogs.Add(b);
db.SaveChanges();
Till this step i created two tables(Blogs and Posts)in my SQlserver.The BlobId is primary key in Blogs table.and foreign key in Posts table.and Blogid in blog table is auto incremented.postid in posts table is also auto incremented
class Program
{
static void Main(string[] args)
{
using (var db = new BlogContext())
{
Console.WriteLine("Enter a name for a new blob:");
var name = Console.ReadLine();
var b = new Blog { Name = name };
db.Blogs.Add(b);
db.SaveChanges();
Here i added name in the blogtable
var id1 = from val in db.Blogs
where val.Name == name
select val.BlobId;
Now by using Name am obtaining the blogid of blogs table
Console.WriteLine("Enter Title:");
var title = Console.ReadLine();
Console.WriteLine("Enter Content");
var content = Console.ReadLine();
var c = new Post { Title = title, Content = content, BlobId = id1};
db.Posts.Add(c);
db.SaveChanges();
here am reading the data for title,content.Then adding the title,content and blogid(which i obtained from another table) into Posts table
I getting error at BlobId = id1
Am getting Cannot implicitly convert type 'System.Linq.IQueryable' to 'int' this error
}
Console.ReadLine();
}
}
Can you help me to solve this.If you did not understand what i explained please reply me
The following query is a sequence of elements, not a scalar value, even though you believe that there is only one result, it is still a collection with one element when the results of the query are iterated over:
var id1 = from val in db.Blogs
where val.Name == name
select val.BlobId;
Change this to:
int id1 = (from val in db.Blogs
where val.Name == name
select val.BlobId).First();
This query will execute immediately and return the first element in the sequence. It will throw an exception if there is no match, so you may want to use FirstOrDefault and assign to a nullable int instead.
Update: Here's a gist that more fully demonstrates the issue https://gist.github.com/pauldambra/5051550
Ah, more update... If I make the Id property on the Mailing class a string then it all works. Should I just give up on integer ids?
I have 2 models
public class Mailing
{
public int Id { get; set; }
public string Sender { get; set; }
public string Subject { get; set; }
public DateTime Created { get; set; }
}
public class Recipient
{
public Recipient()
{
Status = RecipientStatus.Pending;
}
public RecipientStatus Status { get; set; }
public int MailingId { get; set; }
}
On my home page I want to grab the last 10 mailings. With a count of their recipients (eventually with a count of different status recipients but...)
I have made the following index
public class MailingWithRecipientCount : AbstractMultiMapIndexCreationTask<MailingWithRecipientCount.Result>
{
public class Result
{
public int MailingId { get; set; }
public string MailingSubject { get; set; }
public string MailingSender { get; set; }
public int RecipientCount { get; set; }
}
public MailingWithRecipientCount()
{
AddMap<Mailing>(mailings => from mailing in mailings
select new
{
MailingId = mailing.Id,
MailingSender = mailing.Sender,
MailingSubject = mailing.Subject,
RecipientCount = 0
});
AddMap<Recipient>(recipients => from recipient in recipients
select new
{
recipient.MailingId,
MailingSender = (string) null,
MailingSubject = (string)null,
RecipientCount = 1
});
Reduce = results => from result in results
group result by result.MailingId
into g
select new
{
MailingId = g.Key,
MailingSender = g.Select(m => m.MailingSender)
.FirstOrDefault(m => m != null),
MailingSubject = g.Select(m => m.MailingSubject)
.FirstOrDefault(m => m != null),
RecipientCount = g.Sum(r => r.RecipientCount)
};
}
}
I query using
public ActionResult Index()
{
return View(RavenSession
.Query<RavenIndexes.MailingWithRecipientCount.Result, RavenIndexes.MailingWithRecipientCount>()
.OrderByDescending(m => m.MailingId)
.Take(10)
.ToList());
}
And I get:
System.FormatException: System.FormatException : Input string was not
in a correct format. at System.Number.StringToNumber(String str,
NumberStyles options, NumberBuffer& number, NumberFormatInfo info,
Boolean parseDecimal)
Any help appreciated
Yes, integer ids are a pain. This is mainly because Raven always stores a full string document key, and you have to think about when you are using the key or your own id and translate appropriately. When reducing, you also need to align the int and string data types.
The minimum to get your test to pass is:
// in the "mailings" map
MailingId = mailing.Id.ToString().Split('/')[1],
// in the reduce
MailingId = g.Key.ToString(),
However - you could make your index a whole lot smaller and perform better by taking the sender and subject strings out of it. You can just put them in with a transform.
Here is a simplified complete index that does the same thing.
public class MailingWithRecipientCount : AbstractIndexCreationTask<Recipient, MailingWithRecipientCount.Result>
{
public class Result
{
public int MailingId { get; set; }
public string MailingSubject { get; set; }
public string MailingSender { get; set; }
public int RecipientCount { get; set; }
}
public MailingWithRecipientCount()
{
Map = recipients => from recipient in recipients
select new
{
recipient.MailingId,
RecipientCount = 1
};
Reduce = results => from result in results
group result by result.MailingId
into g
select new
{
MailingId = g.Key,
RecipientCount = g.Sum(r => r.RecipientCount)
};
TransformResults = (database, results) =>
from result in results
let mailing = database.Load<Mailing>("mailings/" + result.MailingId)
select new
{
result.MailingId,
MailingSubject = mailing.Subject,
MailingSender = mailing.Sender,
result.RecipientCount
};
}
}
As an aside, did you know about the RavenDB.Tests.Helpers package? It provides a simple base class RavenTestBase that you can inherit from that does most all of the legwork for you.
using (var store = NewDocumentStore())
{
// now you have an initialized, in-memory, embedded document store.
}
Also - you probably shouldn't scan the assembly for indexes in a unit test. You might introduce indexes that weren't part of what you were testing. The better route is to create the index indvidually, like this:
documentStore.ExecuteIndex(new MailingWithRecipientCount());
I have used automapper for mapping lists in the past, for for some reason it won't work in this case.
public class MyType1 {
public int Id { get; set; }
public string Description { get; set; }
}
public class MyType2 {
public int Id { get; set; }
public string Description { get; set; }
}
public void DoTheMap() {
Mapper.CreateMap<MyType2, MyType1>();
Mapper.AssertConfigurationIsValid();
var theDto1 = new MyType2() { Id = 1, Description = "desc" };
var theDto2 = new MyType2() { Id = 2, Description = "desc2" };
List<MyType2> type2List = new List<MyType2> { theDto1, theDto2 };
List<MyType1> type1List = Mapper.DynamicMap<List<MyType1>>(type2List);
//FAILURE. NO EXCEPTION, BUT ZERO VALUES
List<MyType1> type1List2 =type2List.Select(Mapper.DynamicMap<MyType1>).ToList();
//SUCCESS, WITH LINQ SELECT
}
Change this:
Mapper.DynamicMap<List<MyType1>>(type2List)
To this:
Mapper.Map<List<MyType1>, List<MyType2>>(type2List);
DynamicMap is only if you don't know the type at compile time - for things like anonymous types.