The ABP multi lingual mapping was not being called by AutoMapper ProjectTo. If it work, it suppose to map the translation entity to DTO's name property. It works with my old code without using ProjectTo. Please refer to the sample code below:
NOTE : I am using ABP version 4.8.1
NOTE : I have included the Translation DTO in ProductDTO to force auto mapper's projectTo to eagerly load Translation data when generating the IQueryable.
ProductAppService.cs
public async Task<ICollection<ProductListDto>> GetAll()
{
return await repository.GetAll().ProjectTo<ProductListDto>().ToListAsync();
}
Product.cs
public class Product : Entity, IMultiLingualEntity<ProductTranslation>
{
public ICollection<ProductTranslation> Translations { get; set; }
}
ProductTranslation.cs
public class ProductTranslation : Entity, IEntityTranslation<Product>
{
public string Name { get; set; }
}
ProductDto.cs
public class ProductListDto
{
// Mapped from ProductTranslation.Name
public string Name { get; set; }
// Purposely include the translations dto here to force automapper to eagerly load the translation
// data from DB
[JsonIgnore]
public ICollection<ProductTranslationDto> Translations { get; set; }
}
Module.cs
public override void PreInitialize()
{
Configuration.Modules.AbpAutoMapper().Configurators.Add(cfg =>
{
MultiLingualMapContext context = new MultiLingualMapContext(IocManager.Resolve<ISettingManager>());
cfg.DisableConstructorMapping();
cfg.AddCollectionMappers();
CustomDtoMapper.CreateMappings(cfg);
});
}
CustomDtoMapper.cs
cfg.CreateMultiLingualMap<Product, ProductTranslation, ProductListDto>(context);
could you check those links, it appears to be an old issue based on github
https://github.com/aspnetboilerplate/aspnetboilerplate/issues/3356
https://github.com/aspnetboilerplate/aspnetboilerplate/blob/e0ded5d8702f389aa1f5947d3446f16aec845287/test/Abp.ZeroCore.Tests/Zero/MultiLingual/MultiLingual_Entity_Tests.cs
https://github.com/aspnetboilerplate/aspnetboilerplate/blob/e0ded5d870/test/Abp.ZeroCore.SampleApp/Application/Shop/ProductAppService.cs#L48
And Here is an StackOverFlow Question about it.
Data localization in mapping in ASP.NET Zero
Please let me know if one of them works for you.
Related
I often have Models and Dtos like this:
// DTO class
public class OrderDto
{
public int Id { get; set; }
public DateTime Date { get set; }
public int ProductId { get; set; }
// ...
}
// Model class
public class Order : BaseObject
{
public DateTime Date { get set; }
public Product Product { get; set; }
// ...
}
// Model class
public class Product : BaseObject
{
// ...
}
In order to map my OrderDto to the Order class I have to configure AutoMapper for this particular "association" like so:
CreateMap<OrderDto, Order>()
.ForMember(m => m.Product, d => d.ResolveUsing((d, m) =>
{
return m.Session.GetObjectByKey<Product>(dto.ProductId);
}))
This is quite cumbersome to do this for each case like this. Therefore I was looking into generalizing this behaviour by using a custom TypeConverter class:
public class IntBaseObjectTypeConverter : ITypeConverter<int, BaseObject>
{
private UnitOfWork uow;
// ...
public BaseObjectConvert(int source, BaseObject destination, ResolutionContext context)
{
return uow.Session.GetObjectByKey(typeof(destination), source);
}
}
However, this will fail of course if the destination is null. Unfortunately the ResolutionContext does not give me any clue about the specific type of the destination property.
Now my question is, if there is another way to achieve with AutoMapper what I would like to do?
Please note that I am not using Entity Framework which of course would solve this issue on the model level with foreign key and navigational properties. I use XPO from DevExpress which does not allow foreign key and navigational properties like in Entity Framework.
We are working with AutoMapper 4.1.1 (will soon update to 6, but we need some time to change the project) and have this issue: in the source class we have a property "int? MyProp" while in the destination we have string "MyPropValue", which should not be mapped from MyProp. AutoMapper however does map it when the value of MyProp is not null (using MyProp.Value, flattened). We could use Ignore() to ignore the mapping but as we have dozens of such properties, we are looking for a way to do it with one configuration, without breaking the rest of the mapping.
Here is some example code:
class Program
{
static void Main(string[] args)
{
var map = Mapper.CreateMap<Dto, Model>();
var dto = new Dto() {MyProp = 10};
var model = Mapper.Map<Dto, Model>(dto);
Console.WriteLine(model.MyPropValue); // MyPropValue should be empty but it gets mapped
}
}
public class Dto
{
public int? MyProp { get; set; }
}
public class Model
{
public string MyPropValue { get; set; }
}
Mapper.CreateMap<SourceType, DestinationType>()
.ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));
Allows you to skip all nulls.
Automapper skip null values with custom resolver
So, I'm working with ServiceStack and love what it offers. We've come to a point where I'm needing to implement a queryable data API... prior to my coming to this project, a half backed OData implementation was done. I'd rather not try and weed through that to make it work.
Which brings me to AutoQuery. I'd like to try it with our SQL Server database. I'm looking at the examples at http://docs.servicestack.net/autoquery-rdbms - but I cannot for the life of me get this to work. Is there something I'm missing here?
I'm using ORMLite to query SQL, and my integration tests I've written show it to be working as I would expect. I have registered the OrmLiteConnectionFactory in the container, as well as my repository which uses it by way of dependency injection.
Specific to code so far, I have a type, and a message that is based on QueryDb:
public class Detail
{
public string Div { get; set; }
public string Reg { get; set; }
}
[Route("/report/detail")]
public class DetailQuery : QueryDb<Detail>
{
public string[] Div { get; set; }
public string[] Reg { get; set; }
}
The message, DetailQuery, is used by my service:
public class ReportService : Service
{
public object Get(DetailQuery dq)
{
// not sure what to put here?
}
}
With all of that, I am able to see the AutoQuery service instance in the admin interface. When I play with the query interface, I hit my service endpoint, and I see the data I expect - filter values in the 'Div' and 'Reg' collections. What am I missing for this to 'just work' here? I have done plenty in ServiceStack accessing my repositories from the Service itself, but I'm trying to gain some insight into what AutoQuery brings to the table here. I have yet to see a 'straight forward' example of how this works... or am I looking for a pot of gold that just isn't there?
AutoQuery works with just the Request DTO i.e. it doesn't need any Service implementation, so your query:
[Route("/report/detail")]
public class DetailQuery : QueryDb<Detail>
{
public string[] Div { get; set; }
public string[] Reg { get; set; }
}
When called from /report/detail will query the Detail RDBMS Table. But your properties here either need to match a column on the Detail table (e.g. Div or Reg) in order to have an exact match (default), however exact matches aren't normally done with arrays they're done with scalar values like a string, e.g:
public string Div { get; set; }
public string Reg { get; set; }
If you're querying a collection you'd be instead making an IN Query where the values would contain list of values, in which case they're normally pluralized:
public string[] Divs { get; set; }
public string[] Regs { get; set; }
and can be called with:
/report/detail?Divs=A,B&Regs=C,D
Which will perform a query similar to:
SELECT * FROM Detail WHERE Div IN ('A','B') AND Rev IN ('C','D')
If that's not the behavior you want it needs to match an implicit convention, e.g:
public string[] DivBetween { get; set; }
Which will then query:
SELECT * FROM Detail WHERE Div BETWEEN 'A' AND 'B'
If you wanted to you could override the AutoQuery service with a custom implementation, e.g:
public class MyQueryServices : Service
{
public IAutoQueryDb AutoQuery { get; set; }
//Override with custom implementation
public object Any(DetailQuery query)
{
var q = AutoQuery.CreateQuery(query, base.Request);
return AutoQuery.Execute(request, q);
}
}
But you'd only need to do that when you want to customize the default behavior, e.g. add an extra filter to the populated SqlExpression.
I am getting an Object reference not set to an instance of an object error when trying to add multiple entity levels to my EF context.
Take the following three-level example class structure:
public class Forum
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Blog> Blogs { get; set; }
}
public class Blog
{
public int ID { get; set; }
public string Name { get; set; }
public int ForumID { get; set; }
public virtual Forum Forum { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int ID { get; set; }
public string Name { get; set; }
public int BlogID { get; set; }
public virtual Blog Blog { get; set; }
}
For a given Forum, I want to add a new Blog with a new Post:
Forum MyForum = context.Forums.Find(1);
Blog MyBlog = new Blog { Name = "My New Blog" };
Post MyPost = new Post { Name = "My New Post" };
MyForum.Blogs.Add(MyBlog); // This WORKS
MyBlog.Posts.Add(MyPost); // This FAILS
context.SaveChanges(); // We never make it this far
I've tried every possible order combination, including placing context.SaveChanges() immediately after .Add(MyBlog). It seems like it's choking because there is no Blog.ID to use for Post.BlogID, but EF generates temporary key values for use in this situation.
Any ideas?
Hints at the answer (and the root problem) can be found at:
Entity Framework Uninitialised Collection
Entity Framework 4.1 Code First - Should many relationship ICollections be initialised
The "simple" solution is to manually initialize the Blog.Posts collection:
Blog MyBlog = new Blog { Name = "My New Post", Posts = new List<Post>() };
Alternatively, you can build this logic into the class constructor as recommended by Ladislav in the second link.
Basically, when you create a new object, the collection is null and not initialized as a List<>, so the .Add() call fails. The Forum.Blogs collection is able to lazy-load because it derives from the database context. Blog.Posts, however, is created from scratch, and EF can't help you, so the collection is null by default.
I just start to learn REST and ServiceStack and there's something about Route that I just can't quite understand. For example if we take the very basic HelloWorld example from GitHub tutorial and re-write it to return collection of User objects. Here is example:
public User
{
public string Name;
public string Address;
public int Age;
}
// Hello - request object without [Route] attribute
public class Hello
{
public string Name { get; set; }
}
public class HelloResponse
{
public IEnumerable<User> Result {get;set;}
}
public class HelloService : Service
{
public object Any(Hello request)
{
return new HelloResponse { // Collection of User object };
}
}
now everything working right and no problems here. But now I want to add another routing url like: /Hello/{name}/Address
Actually this call (GET) to this url will return a single User selected by Age parameter. How I can do this ? Should I add another Service ? And if the url will be:
/Hello/{name}/{age}/Address
It seems I don't understand something.....
See this earlier answer for details about Routing in ServiceStack. The Smart Routing section in ServiceStack's New API explains further options and different precedence.
There are a few problems with your example. First ServiceStack text serializers only support public properties so you need to change your User Model to use public properties instead of fields, e.g:
public User
{
public string Name { get; set; }
public string Address { get; set; }
public int Age { get; set; }
}
Next, Interfaces on DTOs are a bad idea as there's no good reason for it. They're still supported but you can end up with undesirable results. Use a concrete collection like a List<T> which provides more utility, e.g:
public class HelloResponse
{
public List<User> Results { get; set; }
}
Also the routes should match the property names on your DTO exactly, they are case-insensitive when matching against the Request Path, but they need to map to an exact property name, e.g:
/Hello/{Name}/{Age}/Address