I have a simple object with a default constructor. The objects I'm mapping to and from are both defined exactly the same and I've configured a mapper for them. Everything works fine getting the object from the database.
public class Tag
{
public Guid ProjectId { get; set; }
public Guid TagId { get; set; }
public string Name { get; set; }
}
If I call Mapper.Instance.Map(tagFrom, tagTo); everything works fine, but if I call dbContext.Tags.Persist().InsertOrUpdate(tag); I get this error.
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
AutoMapper created this type map for you, but your types cannot be mapped using the current configuration.
AutoMapper created this type map for you, but your types cannot be mapped using the current configuration.
Tag -> Expression1 (Destination member list)
AKS.Common.Models.Tag -> System.Linq.Expressions.Expression1[[System.Func`2[[AKS.AppCore.Entities.Tag, AKS.AppCore, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[System.Boolean, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]] (Destination member list)
Unmapped properties:
Parameters
No available constructor.
Seems like it's saying I have no default constructor, but clearly there is one. Anyone know what I'm doing wrong?
Package Versions:
AutoMapper 8.0.0
AutoMapper.Collections 5.0.0
AutoMapper.Collections.EntityFrameworkCore 0.2.0
This may be a bug in AutoMapper.Collections.EntityFrameworkCore, or it may have been caused by my own misuse.
I was configuring my Mapper with the following code
var cfg = new MapperConfigurationExpression();
cfg.AddCollectionMappers();
cfg.UseEntityFrameworkCoreModel<MyDbContext>();
cfg.CreateMap<TagDto, TagEntity>().ReverseMap();
Then I was trying to validate my mapping config with this code before intializing the mapper.
var config = new MapperConfiguration(cfg);
config.AssertConfigurationIsValid();
Mapper.Initialize(cfg);
If I remove the lines where I create a MapperConfiguration and use that to AssertConfigurationIsValid() then calls to InsertOrUpdate() work.
I also found that I can call AssertConfigurationIsValid() if I initialize my mapper first, then call that method on the Mapper.Instance
Mapper.Initialize(cfg);
Mapper.Configuration.AssertConfigurationIsValid();
Related
Imagine you have the following simplified CustomerRequest class:
public class CustomerRequest : IReturn<CustomerResponse>
{
public string OrgNumber { get; set; }
}
For this request, you have the following validator:
public CustomerValidator()
{
RuleFor(r => r.OrgNumber).NotEmpty();
}
If you view ServiceStack's auto generated metadata page on http://[myService]/json/metadata?op=CustomerRequest, it will look like this:
NAME PARAMETER DATA TYPE REQUIRED
OrgNumber body string No
As you can see, the parameter is marked as "Required: No" even though the validator requires it to exist and be not empty.
Is it possible to reflect the validator's rules in the metadata automatically? I know I can use [ApiMember(IsRequired = false)], but I'd prefer to have it tied to the validator if possible.
Only declarative attributes show up on the metadata Pages as they can be statically inferred, any validators registered at runtime are opaque and cannot be statically inferred by ServiceStack.
I've got a POCO defined, something like this:
public class Customer
{
public string Name { get; set; }
public DateTime DOB { get; set; }
[System.ComponentModel.DataAnnotations.Schema.NotMapped] // <- this is what I want to do, but can't in PCL
public AccountCollection Accounts { get; set; }
}
The above has the "NotMapped" attribute, which is what I want - but it's not available in a portable class library (PCL). The thing is, the class I need is defined in an assembly that WILL be used on the portable device but it will be filled from entity framework on the web, which DOES have access to the NotMapped attribute. If I could find a way to add the property to EF's "NotMapped" list, that would be ideal.
Is there a way to get this to work? That is, a way to do what "NotMapped" does programmatically?
I've considered other workarounds, but none of them are ideal:
Could create a DAL separate from my domain layer and translate
between the two (but requires mapping and two models instead of one)
Could write custom EF queries and updates to ignore the property (but means writing all the linq/SQL/procs myself)
Found the answer in the Context's OnModelCreating() overload. Accessing the modelBuilder parameter it's possible to find the entity and ignore specific properties. This works even when the POCO is defined in a PCL.
For example:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Ignore Customer.Accounts
modelBuilder.Entity<Customer>().Ignore(c => c.Accounts);
}
I must be missing something obvious but the following fails with a compile error:
internal static IEnumerable<T> GetEntitiesWithCommaSeparatedRowKeys<T>(
string tableName, string partitionKey,
string commaDelimitedStringOfRowKeys) where T: TableEntity
{
....
TableQuery<T> entitiesQuery = new TableQuery<T>().Where(
TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("PartitionKey",
QueryComparisons.Equal, partitionKey),
TableOperators.And,
AzureHelper.GetFilterConditionForCommaDelimitedStringOfRowKeys(commaDelimitedStringOfRowKeys)
));
// compile error on this line
IEnumerable<T> entities = table.ExecuteQuery<T>(entitiesQuery);
...
}
The error I get is:
'T' must be a non-abstract type with a public parameterless constructor
in order to use it as parameter 'TElement' in the generic type or
method 'Microsoft.WindowsAzure.Storage.Table.CloudTable.ExecuteQuery<TElement>
(Microsoft.WindowsAzure.Storage.Table.TableQuery<TElement>,
Microsoft.WindowsAzure.Storage.Table.TableRequestOptions,
Microsoft.WindowsAzure.Storage.OperationContext)'
TableEntity clearly has a public parameterless constructor and is non-abstract.
The following is the object from metadata info when I hit F12 on TableEntity (just to ensure its resolving the TableEntity type correctly).
namespace Microsoft.WindowsAzure.Storage.Table
{
public class TableEntity : ITableEntity
{
// Summary:
// Initializes a new instance of the Microsoft.WindowsAzure.Storage.Table.TableEntity
// class.
public TableEntity();
...
}
}
Any ideas anyone?
FYI I'm using Azure Client library 3.0.1.0.
UPDATE: Added linked issue which solved a similar problem
So turns out that if a method provides a constraint on type, that constraint must be also forwarded by any callers of that type.
I didn't know that.
In this case the definition for Table.ExecuteQuery looks like the following
public IEnumerable<TElement> ExecuteQuery<TElement>(TableQuery<TElement> query,
TableRequestOptions requestOptions = null,
OperationContext operationContext = null)
where TElement : ITableEntity, new();
Therefore adding new() to my constraints for T fixes the issue.
So the final method declaration looks like
internal static IEnumerable<T> GetEntitiesWithCommaSeparatedRowKeys<T>(string tableName,
string partitionKey,
string commaDelimitedStringOfRowKeys)
where T : TableEntity , new() //This is new (pun intended :))
Found a related issue in one of the related links that showed up for this question.
I'm guessing the compiler could always look up the type constraints from when I actually make the call, but since TableEntity is public (and not sealed) I guess it could end up being a runtime issue.
Also interesting to note that my method is marked internal which should really enable the compiler to check against callers within the library.
Anyways, learnt something new :).
I'm maintaining an app which is using AutoMapper like this:
public class UserDomainService
{
public UserDTO GetUser(int id)
{
Mapper.Reset();
Mapper.CreateMap<User, UserDTO>();
var user = ....;
return Mapper.Map<User, UserDTO>(user);
}
}
This domain service is used by web-services.
I think it can be a problem when two web-service requests come in and on separate threads Reset and Map are called.
The Mapper can become in a state where the Map() fails.
I know I should probably setup CreateMap() mappings in Application_Start, but for now I am trying to do this:
public class UserDomainService
{
public UserDTO GetUser(int id)
{
var config = new AutoMapper.Configuration(new TypeMapFactory(), MapperRegistry.AllMappers());
config.CreateMap<User, UserDTO>();
var mapper = new MappingEngine(configuration);
var user = ....;
return mapper.Map<User, UserDTO>(user);
}
}
Leaving aside performance, is it anything which could potentially make the app crash?
Sometimes I am getting an exception like this:
System.ArgumentException: An item with the same key has already been added.
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
at AutoMapper.TypeMapFactory.GetTypeInfo(Type type)
at AutoMapper.TypeMapFactory.CreateTypeMap(Type sourceType, Type destinationType, IMappingOptions options)
at AutoMapper.Configuration.CreateTypeMap(Type source, Type destination, String profileName)
at AutoMapper.Configuration.CreateMap[TSource,TDestination](String profileName)
at AutoMapper.Configuration.CreateMap[TSource,TDestination]()
Note that the above sample mapping is just an example.
I am using AutoMapper v1.1.0.188 in a 3.5 Net app.
EDIT:
There's a specific reason why it's not easy for me to put the configuration in the Application_Start.
I have different mapping requirements depending on the context. For example, for the same User to UserDTO, I need two different types of mapping.
It's the same problem described in this old question:
Link
https://github.com/AutoMapper/AutoMapper/wiki/Getting-started
Where do I configure AutoMapper?
If you're using the static Mapper method, configuration should only happen once per AppDomain. That means the best place to put the configuration code is in application startup, such as the Global.asax file for ASP.NET applications. Typically, the configuration bootstrapper class is in its own class, and this bootstrapper class is called from the startup method.
I have the following Domain Model:
public class DaybookEnquiry : Entity
{
public DateTime EnquiryDate { get; set; }
[ForeignKey("EnquiryType")]
public int DaybookEnquiryTypeId { get; set; }
public string AccountNumber { get; set; }
[ForeignKey("User")]
public int UserId { get; set; }
#region Navigation Properties
public virtual User User { get; set; }
public virtual DaybookEnquiryType EnquiryType { get; set; }
public virtual ICollection<DaybookQuoteLine> QuoteLines { get; set; }
#endregion
}
This is inside of a project named DomainModel. Entity is just a base class which my domain models inherit from, it contains an Id field.
I then have other projects inside my solution called ServiceInterface and ServiceModel. ServiceInterface contains all my services for my application and ServiceModel contains my DTO's and routes etc.. I'm trying to follow the guidelines set out here: Physical Project Structure
My EnquiriesService contains a method to create a new enquiry in my database using a repository:
public void Post(CreateEnquiry request)
{
// Not sure what to do here..
// _repository.Insert(request);
}
My CreateEnquiry request looks like so:
[Api("POST a single Enquiry for Daybook.")]
[Route("/enquiries", "POST")]
public class CreateEnquiry : IReturnVoid { }
As you can see, the CreateEnquiry request object is empty. Do I need to add properties to it to match my Domain Model and then use AutoMapper or something similar to map the fields to my Domain Model and pass that into my repository?
The Insert method on my repository looks like so:
public virtual void Insert(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Detached)
{
dbEntityEntry.State = EntityState.Added;
}
else
{
DbSet.Add(entity);
}
DbContext.SaveChanges();
}
Yes. Your Service request, in this case CreateEnquiry needs to have all the properties you need in order to do whatever it is you want to do!
I've seen two different models for Create vs Update:
Use one request objects called, say, SetEnquiry that has a nullable id field. When null and using the POST HTTP verb, it internally creates a new object. And when not null and using the PATCH HTTP verb, it internally updates an object. You can use ServiceStack's implementation of AbstractValidator<T> to add logic such as if POST then id field needs to be null; and if PATCH then id field cannot be null. This will help ensure your data is always as it needs to be.
Create two request objects -- one for Create and one for Update. The Create doesn't even have an id field, and the Update has one and requires it. You can use the same validation technique used above, except applied to each class independently, so you don't need the conditional check of if this verb do this; if that verb do that.
How you map to your data model is up to you. You can use something like AutoMapper or you can use ServiceStack's built-in TranslateTo and PopulateWith methods. I personally take a middle ground: I created my own object extension methods called MapTo and MapFrom that interally call TranslateTo and PopulateWith respectively. Why did I do this? Because then I control those extensions inside my own namespaces and when I need to do special mappings (like a column name doesn't match up, or one object is more complex than the other, or I simply want to ignore a particular column from one of the objects) I simply overload the MapTo and MapFrom with explicit types, giving it higher specificity than the generic methods.
So back to your question specifically. Assuming you're using the built in TranslateTo your service method might look like this:
public void Post(CreateEnquiry request)
{
_repository.Insert(request.TranslateTo<Enquiry>());
}
One more thing: I generally return the object itself when doing a Create and Update. As fields can change (auto-calculated fields, for example) I like to return the object back to the caller. This is preference and has no real bearing on the answer I'm giving you. Just throwing it out there!