I have the following models:
public class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
[...]
public int OfficeId { get; set; }
public string OfficeInfo
{
get { return Office.Info; }
}
public Office Office { get; set; }
}
public class Office
{
public int OfficeId { get; set; }
public string Info { get; set; }
}
I have a grid in the client side which rows I want to feed with instances of Employee, including the OfficeInfo in one of the columns, so I'm consuming it through the following query:
"/odata/Employees?$expand=Office&$select=EmployeeId,Name,OfficeInfo"
I have both entities registered in the IEdmModel:
private static IEdmModel GetEDMModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Employee>("Employees");
builder.EntitySet<Office>("Offices");
[...]
}
and my Get action looks like this:
[EnableQuery]
public IQueryable<Employees> Get()
{
[...]
}
but I keep getting this Exception:
"Could not find a property named 'OfficeInfo' on type 'Xds.Entities.Employee'"
What am I missing here?
You could mark the property OfficeInfo as required or add this property explicitly:
Noting as required:
builder
.EntitySet<Employee>("Employees")
.EntityType
.Property(_ => _.OfficeInfo)
.IsRequired();
Adding explicitly:
builder
.EntitySet<Employee>("Employees")
.EntityType
.Property(_ => _.OfficeInfo)
.AddedExplicitly = true;
You can check your model metadata and see whether following appears under 'Xds.Entities.Employee' type.
<Property Name="OfficeInfo" Type="Edm.String" />
Since it is a readonly property, you should turn on isQueryCompositionMode to get it shown in the model, like (can pass real HttpConfiguration there):
ODataModelBuilder builder = new ODataConventionModelBuilder(new System.Web.Http.HttpConfiguration(), true);
After that, the query should work.
Note that flag is marked as for testing purpose, but it should be fine if you manually verify your metadata.
Related
I am trying to use AutoMapper to map a DTO to an Entity class but I keep getting an error.
Here is the DTO Class:
public class Product
{
public string ID { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
public PriceTiers PriceTiers { get; set; }
}
and here is the Entity:
public partial class Product
{
public Product()
{
PriceTiers = new List<PriceTiers>();
}
[Key]
public string ID { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
public virtual ICollection<PriceTiers> PriceTiers { get; set; }
}
Why do I keep getting the following error?
{"Missing type map configuration or unsupported
mapping.\r\n\r\nMapping types:\r\nPriceTiers ->
ICollection1\r\nWeb.Areas.DEAR.DTOs.PriceTiers -> System.Collections.Generic.ICollection1[[Web.Areas.DEAR.Data.PriceTiers,
Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]\r\n\r\n
Destination Member:\r\nPriceTiers\r\n"}
This is what I have in the Profile class:
AllowNullCollections = true;
CreateMap<DTOs.Product, Data.Product>();
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
and this is what I use to map the classes:
var products = _mapper.Map<IEnumerable<Product>>(result.Products);
This is what is in the Program.cs:
builder.Services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
The exception message is quite clear, the AutoMapper doesn't know how to map the data from DTOs.PriceTiers to ICollection<Data.PriceTiers>.
Solution 1: Map from DTOs.PriceTiers to ICollection<Data.PriceTiers>
I believe that Custom Type Converters is what you need.
Create Custom Type Converters.
public class ICollectionDataPriceTiersTypeConverter : ITypeConverter<DTOs.PriceTiers, ICollection<Data.PriceTiers>>
{
public ICollection<Data.PriceTiers> Convert(DTOs.PriceTiers src, ICollection<Data.PriceTiers> dest, ResolutionContext context)
{
if (src == null)
return default;
var singleDest = context.Mapper.Map<Data.PriceTiers>(src);
return new List<Data.PriceTiers>
{
singleDest
};
}
}
Add to mapping profile.
CreateMap<DTOs.PriceTiers, ICollection<Data.PriceTiers>>()
.ConvertUsing<ICollectionDataPriceTiersTypeConverter>();
Demo # .NET Fiddle
Solution 2: Map from ICollection<DTOs.PriceTiers> to ICollection<Data.PriceTiers>
If the PriceTiers in DTOs.Product supports multiple items and mapping with many to many (to ICollection<Data.ProductTiers>), then consider modifying the property as the ICollection<DTOs.PriceTiers> type.
namespace DTOs
{
public class Product
{
...
public ICollection<PriceTiers> PriceTiers { get; set; }
}
}
Did you added "CreateMapper()" method after your configurations?
Try something like that.
public class MappingProfile : Profile
{
public MappingProfile {
AllowNullCollections = true;
CreateMap<DTOs.Product, Data.Product>();
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
}
}
After that, on your container service, inject this dependency:
var mappingConfig = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new MappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
builder.Services.AddSingleton(mapper);
After some more research I found out that my mapping profile was not in the right order. These are the changes I made.
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
AllowNullCollections = true;
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
CreateMap<DTOs.Product, Data.Product>()
.ForMember(dto => dto.PriceTiers, opt => opt.MapFrom(x => x.PriceTiers));
}
}
Now it maps perfectly
Swagger exposes by default any schema that is used by an exposed controller (API end point). How can a schema (class) be exposed if it is not used by a controller?
For example, Swagger is showing the following Schemas:
But, the Song Schema (below) needs to be exposed. It is not exposed because it is not used by a controller (API end point).
using System;
namespace ExampleNamespace
{
public class Song
{
[Key][Required]
public int SongID { get; set; }
[Required]
public string SongName { get; set; }
public string SongDescription { get; set; }
public int SongLength { get; set; } //seconds
[Required]
public int AlbumID { get; set; }
}
}
How can this be accomplished?
You can add a schema using a DocumentFilter
public class AddSongSchemaDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var songSchema = new OpenApiSchema {...};
songSchema.Properties.Add(new KeyValuePair<string, OpenApiSchema>("songName", new OpenApiSchema { ... }));
...
context.SchemaRepository.Schemas.Add("Song", songSchema);
}
}
The class OpenApiSchema is used for the song schema itself, and for property schemas. This type contains a number of documentation related properties you can set such as Description.
You register AddSongSchemaDocumentFilter like so
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(options =>
{
options.DocumentFilter<AddSongSchemaDocumentFilter>();
});
}
This could be a bit tedious if there are many properties. Using reflection, you can iterate on the properties, and even reflect on associated attributes attached to those properties.
var songSchema = new OpenApiSchema() { };
var song = new Song();
var properties = typeof(Song).GetProperties();
foreach (var p in properties)
songSchema.Properties.Add(new KeyValuePair<string, OpenApiSchema(
p.Name,
new OpenApiSchema()
{
Type = p.PropertyType.ToString(),
Description = // get [Description] attribute from p,
// ... etc. for other metadata such as an example if desired
}));
context.SchemaRepository.Schemas.Add("Song", songSchema);
Full Swashbuckle documentation.
We are using AutoMapper (9.0.0) in .net core for mapping values between source and destination. Till time this is working fine. However, we need to keep some of the values in destination as it is after mapping.
We have tried to used UseDestinationValue() and Ignore() methods on member, but it is not preserving the existing values. Below is the code for the same.
RequestModel
public class RequestModel
{
public int Id { get; set; }
public int SubmittedById { get; set; }
public string Description { get; set; }
public string Location { get; set; }
}
RequestDto
public class RequestDto
{
public int Id { get; set; }
public int SubmittedById { get; set; }
public string Description { get; set; }
public string Location { get; set; }
public string SubmittedByName { get; set; }
}
We are accepting Dto in API as request parameter
API
[HttpPost]
public IActionResult Save([FromBody] RequestDto requestDto)
{
// Some logic to save records
}
So, before saving the records we are mapping RequestDto to RequestModel and passing that model to DAL layer to save the records like this
var requestModel = MapperManager.Mapper.Map<RequestDto, RequestModel>(RequestDto);
And call to data layer
var requestModel = DAL.Save(RequestModel)
So, after receiving the updated request model we are again mapping it to requestDto, in this case we are loosing the value for SubmittedByName property.
return MapperManager.Mapper.Map<RequestModel, RequestDto>(requestModel);
Mapper Class
public class RequestProfile: Profile
{
public RequestProfile()
{
CreateMap<RequestModel, RequestDto>()
CreateMap<RequestDto, RequestModel>()
}
}
This SubmittedByName column is not present in the Request table, but we want to utilize its value after saving the records.
So, how can we preserve the destination value after mapping.
Any help on this appreciated !
I think you have to use the Map overload that accepts destination.
This works for me, using same model / dto you posted, in a console application:
var config = new MapperConfiguration(cfg => cfg.CreateMap<RequestModel, RequestDto>().ReverseMap());
var mapper = config.CreateMapper();
var source = new RequestDto
{
Id = 1,
SubmittedById = 100,
SubmittedByName = "User 100",
Description = "Item 1",
Location = "Location 1"
};
Console.WriteLine($"Name (original): {source.SubmittedByName}");
var destination = mapper.Map<RequestDto, RequestModel>(source);
Console.WriteLine($"Name (intermediate): {source.SubmittedByName}");
source = mapper.Map<RequestModel, RequestDto>(destination, source);
Console.WriteLine($"Name (final): {source.SubmittedByName}");
The standard Map method creates a new object but the overloaded method uses existing object as destination.
We have tried to used UseDestinationValue() and Ignore() methods on member, but it is not preserving the existing values. Below is the code for the same.
since that didn't work for you
I would suggest creating a generic class like this (assuming you have multiple classes of RequestDto)
class RequesterInfo<T>
{
public string RequesterName { get; set; } // props you want to preserve
public T RequestDto { get; set; } // props to be mapped
}
by keeping the mapping as it is,
and modifying your code to something like this:
var requestModel = MapperManager.Mapper.Map<RequestDto, RequestModel>(RequesterInfo.RequestDto);
so what happens is that you modify the T RequestDto part of the object without modifying other properties.
I get this error when trying to add something on my db through EF Core.
System.InvalidOperationException: 'No suitable constructor found for
entity type 'HealthCheck'. The following constructors had parameters
that could not be bound to properties of the entity type: cannot bind
'hctype' in 'HealthCheck(string title, string hctype, string link)'.'
This is my HealthCheck class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Application.Models
{
public class HealthCheck
{
public HealthCheck(string title, string hctype, string link)
{
Title = title;
HCType = hctype;
Link = link;
}
public int Id { get; set; }
public string Title { get; set; }
public string HCType { get; set; }
public string Link { get; set; }
}
}
My RepositoryContext
using Microsoft.EntityFrameworkCore;
using Application.Models;
namespace Application.Repository
{
public class RepositoryContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
#"Server=(localdb)\mssqllocaldb;Database=healthcheck;Integrated Security=True");
}
//public DbSet<HealthCheck> HealthChecks { get; set; }
//public DbSet<UserHealthCheck> UserHealthChecks { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<HealthCheck>().ToTable("HealthCheck");
modelBuilder.Entity<UserHealthCheck>().ToTable("UserHealthCheck");
}
}
}
My Repository
using Application.Models;
namespace Application.Repository
{
public class Repository
{
public void InsertHealthCheck(HealthCheck healthCheck)
{
using (var db = new RepositoryContext())
{
db.Add(healthCheck);
db.SaveChanges();
}
}
}
}
And this is where "InsertHealthCheck()" is being called from
[Route("/api/HealthCheck/Website")]
[HttpPost]
public ActionResult WebsiteStatus([FromBody] WebsiteDataModel websiteData)
{
HealthCheck data = new HealthCheck(websiteData.Title, "Website", websiteData.Url);
try
{
HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(websiteData.Url);
HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();
HttpStatusCode HealthCheckStatusCode = myHttpWebResponse.StatusCode;
myHttpWebResponse.Close();
return Ok(HealthCheckStatusCode);
}
catch(UriFormatException)
{
return Ok("Check url.");
}
catch (Exception)
{
return Ok("400");
}
finally
{
repository.InsertHealthCheck(data);
}
}
If you can give me a hand I would appreciate it, if you need for me to post any other part of the code just ask.
Also, I literally just started learning EF Core, so if I did something really stupid, point it out
Providing a parameterless constructor avoids the problem, but it was not the true cause of the error in the OP's case. EF Core 2.1 and higher uses a strict convention to map constructor parameters to property names of the entity. It expects a constructor parameter's name to be a true camel-case representation of a property name in Pascal-case. If you change the parameter name from "hctype" to "hCType", you should not get the error and not have to provide a parameterless constructor if your approach to domain-driven design dictates that it would be problematic to do so.
If, however, you were simply providing the parameterized constructor as a convenience but it is not improper for a caller to be able to instantiate a HealthCheck with the "new" operator, then simply adding the parameterless constructor is acceptable.
You are missing empty constructor:
public class HealthCheck
{
// here
public HealthCheck()
{
}
public HealthCheck(string title, string hctype, string link)
{
Title = title;
HCType = hctype;
Link = link;
}
public int Id { get; set; }
public string Title { get; set; }
public string HCType { get; set; }
public string Link { get; set; }
}
try it like that
I got this exception.
No suitable constructor was found for entity type 'Blog'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'name' in 'Blog(string name)'; cannot bind 'name' in 'Blog(int id, string name)'.'
I mitigated this in two ways.
First take a look at my Blog class.
public class Blog
{
public Blog(string name)
{
Name = name;
}
private Blog(int id, string name)
{
Id = id;
Name = name;
}
public int Id { get; private set; }
public string Name { get; } // Note: No setter, it's a readonly prop.
public List<Post> Posts { get; } = new();
}
Note there is not setter for Name.
So the first approach was to add private setter. Now its no more a readonly prop. It can be set from within the class.
public string Name { get; private set; }
The app ran without exception.
But if you insist on keeping the Name prop readonly here is another way. We need to add and explicit configuration to the context as follows. Here we tell Ef Core to explicitly map it. Ef Core does not map readonly properties by default.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Ef does not map readonly properties by default, we have to
// ask ef to map the Name even though Name is readonly
modelBuilder.Entity<Blog>(b => b.Property(e => e.Name));
}
As the HealthCheck class is representing a Table in your database, so you have to provide an empty constructor.
Try the following implementation and let me know if you still receive the same error:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Application.Models
{
public class HealthCheck
{
public HealthCheck()
{
}
public HealthCheck(string title, string hctype, string link)
{
Title = title;
HCType = hctype;
Link = link;
}
public int Id { get; set; }
public string Title { get; set; }
public string HCType { get; set; }
public string Link { get; set; }
}
}
So many ways of getting this error :-( Here's yet another one:
read-only properties are not mapped by convention [... even if the PropertyName / constructorParameterNames accurately follow the PascalCase/camelCase matching convention ...], and hence there are no mapped properties that match the constructor. Mapping the properties like this should result in the constructor being used without error:
modelBuilder.Entity<A>(b =>
{
b.Property(e => e.Id).ValueGeneratedNever();
b.Property(e => e.Name);
});
https://github.com/dotnet/efcore/issues/14336
But here is a summary of the rules for ef core as at 2021, which should cover most cans and can'ts:
https://learn.microsoft.com/en-us/ef/core/modeling/constructors
I'm trying to get Fluent NHibernate to map a collection for me. My class definitions are as follows:
public abstract class Team
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
public class ClientTeam : Team
{
public virtual IEnumerable<Client> Clients { get; set; }
}
public class Client
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual string Identifiers { get; set; }
}
My mappings:
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
Table("Team");
Id(x => x.Id).GeneratedBy.Assigned();
Map(t => t.TeamName);
}
}
public class ClientTeamMap : SubclassMap<ClientTeam>
{
public ClientTeamMap()
{
HasMany(t => t.Clients);
}
}
public class ClientMap : ClassMap<Client>
{
public ClientMap()
{
Table("Client");
Id(c => c.Id);
Map(c => c.Name);
Map(c => c.Identifiers);
}
}
I've built a unit test that instantiates a team and then attempts to persist it (the test base has dependency configuration, etc. in it):
public class TeamMapTester : DataTestBase
{
[Test]
public void Should_persist_and_reload_team()
{
var team = new ClientTeamDetail
{
Id = Guid.NewGuid(),
TeamName = "Team Rocket",
Clients = new[]
{
new ClientDetail {ClientName = "Client1", ClientIdentifiers = "1,2,3"}
}
};
using (ISession session = GetSession())
{
session.SaveOrUpdate(team);
session.Flush();
}
AssertObjectWasPersisted(team);
}
}
When I run the test, I get this error:
SetUp : FluentNHibernate.Cfg.FluentConfigurationException : An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
Database was not configured through Database method.
----> NHibernate.MappingException: Could not compile the mapping document: (XmlDocument)
----> NHibernate.PropertyNotFoundException : Could not find field '_clients' in class 'ClientTeam'`
I've looked through the NHibernate documentation and done some google searching, but I can't find anything that appears to address this issue. The documentation for Fluent NHibernate's Referencing methods explicitly uses auto properties, so I'm sure that's not the issue.
Why might NHibernate think that _clients is the field it should map in this case?
And the reason turns out to be: Conventions.
The Fluent mappings were set up to try to enforce read-only collection properties, by requiring a backing field. The ICollectionConvention in question:
public class CollectionAccessConvention : ICollectionConvention
{
public void Apply(ICollectionInstance instance)
{
instance.Fetch.Join();
instance.Not.LazyLoad();
instance.Access.CamelCaseField(CamelCasePrefix.Underscore);
}
}
which requires that collection backing fields be camelCased and start with an underscore.