Automapper returns empty objects using ProjectTo<> - entity-framework

I'm new to Automapper, and trying to map a Entity Framework database object to a DTO.
My database object, OfficeLookup, contains Code and Description (among other properties I'm not concerned with).
My OfficeDto contains Id and Name properties.
The mapping:
private MapperConfiguration OfficeMapperConfiguration =>
new MapperConfiguration(
cfg =>
{
cfg.CreateMap<OfficeLookup, OfficeDto>()
.ForMember(dest => dest.Id, act => act.MapFrom(src => src.Code))
.ForMember(dest => dest.Name, act => act.MapFrom(src => src.Description));
});
And my code:
public IEnumerable<OfficeDto> GetOfficeDtos() => OfficeLookup.ProjectTo<OfficeDto>(OfficeMapperConfiguration); // returns an Ienumerable of empty OfficeDTOs
The strange thing is that I have another, more complex mapping for a different table which works fine. I don't understand why this map doesn't.
The expression generated by my mapping is
ObjectQuery<OfficeLookup>.MergeAs(MergeOption.AppendOnly).Select(dtoOfficeLookup => new OfficeDto())
which doesn't look right. The other mapping generates an expression which set the various properties correctly (with the exception of the Office value. I assume that once I can get this standalone mapping, I will be able to fix the nested mapping).

The problem should be in corresponding OfficeDto properties, and more specifically, the lack of property setter, in which case AutoMapper simply skips them from projection even though they have been mapped explicitly.
e.g. the issue is reproduced with the following class:
class OfficeDto
{
public int Id { get; }
public string Name { get; }
}
And adding property setters (even private) fixes it:
class OfficeDto
{
public int Id { get; private set; }
public string Name { get; set; }
}

Related

Entity Framework Core 5.0 - Many to many select query

I am trying to get a single User, with a list of Items, mapped with a many-to-many entity UserItems. However, I am unable to retrieve the mapped Items due to to an error that I'm unable to solve (error at bottom of question). Here is my code:
public class User
{
public int Id { get; set; }
public ICollection<UserItem> UserItems { get; set; }
}
public class Item
{
public int Id { get; set; }
public ICollection<UserItem> UserItems { get; set; }
}
public class UserItem
{
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public int ItemId { get; set; }
public Item Item { get; set; }
public int Quantity { get; set; }
}
The UserItem class configuration has the following relationships defined:
builder.HasOne(x => x.User)
.WithMany(x => x.UserItems)
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.ClientCascade);
builder.HasOne(x => x.Item)
.WithMany(x => x.UserItems)
.HasForeignKey(x => x.ItemId)
.OnDelete(DeleteBehavior.ClientCascade);
I have the following generic repo with this method:
public class GenericRepository<T> : where T : class
{
private readonly DbContext _context;
public GenericRepository(DbContext context) => _context = context;
public T Get(Expression<Func<T, bool>> where, params Expression<Func<T, object>>[] navigationProperties)
{
IQueryable<T> query = _context.Set<T>();
query = navigationProperties.Aggregate(query, (current, property) => current.Include(property));
var entity = query.FirstOrDefault(where);
return entity;
}
}
However, when I try to run the code, I get an error on the Select(x => x.Item):
var user = repo.Get(x => x.Id == 1, x => x.UserItems.Select(y => y.Item));
Error:
System.InvalidOperationException: 'The expression 'x.UserItems.AsQueryable().Select(y => y.Item)' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.'
What am I doing wrong, this seems to work for my other projects?
This error Occurs because you are not passing in a navigation property (x.UserItems would be a navigation property) but rather something you want to do with the navigation property. UserItems.Select(y => y.Item) is not a property of x because Select() is a function and therefore it cannot be included.
What you are trying to do (I assume it is including UserItems and also the corresponding Items) is not going to work with your current implementation of the repository. To include navigation properties of navigation properties .ThenInclude() must be used instead of .Include() which works only for navigation properties directly defined on the Entity the DbSet is created for.
But apart from your question I would suggest not to use such an generic implementation of Repository. The main benefit from using reposiories is to separarte code related to loading and storing of entities from the rest of your code. In your case if the consumer of repository knows that navigation properties must be included and that he has to provide them - then what is the point of having a repository at all? Then the consumer again cares about database specific code which makes having a repository unneccessary. I would recommend just making a conrete "UserRepository" which can only be used to retrieve users and explicitly includes the needed properties.

Make reference table data read-only - EF Core

I have a table (Commodity) which has a one-to-one relationship with another table (CommodityMaterial), in my GET endpoint the Commodity returns it's own columns and also the columns (and values) of the referenced table which works perfectly. However, in the POST operation of the endpoint, a user should not be able to POST data of the reference table (CommodityMaterial), how can this be achieved? I used to disable this by using a DataContract, however, because I need the columns for my GET operator, this is not an option.
I already tried, following this post: https://csharp.christiannagel.com/2016/11/07/efcorefields/, removing the SET on the reference table and making a backing field but this does not seem to work (error that the backing field is read-only).
I also tried setting the SET to protected, but this is not working.
So the question is, how to make the reference table read-only (only available for my GET endpoint and not my POST endpoint).
The Commodity POCO class:
[DataContract]
public class Commodity
{
public Commodity()
{
}
public Commodity(CommodityMaterial commodityMaterial)
{
CommodityMaterial = commodityMaterial;
}
[DataMember]
public long CommodityID { get; set; }
[DataMember]
public long CommodityMaterialID { get; set; }
[DataMember]
public decimal? SpecficWeight { get; set; }
[DataMember]
public CommodityMaterial CommodityMaterial { get; }
}
Fluent part:
modelBuilder.Entity<Commodity>(entity =>
{
entity.Property(e => e.CommodityID)
.HasColumnName("CommodityID")
.ValueGeneratedOnAdd();
entity.Property(e => e.CommodityMaterialID)
.HasColumnName("CommodityMaterialID");
entity.Property(e => e.SpecficWeight)
.HasColumnName("SpecficWeight")
.HasColumnType("decimal(18, 2)");
entity.HasOne(a => a.CommodityMaterial)
.WithOne(b => b.Commodity)
.HasForeignKey<Commodity>(b => b.CommodityMaterialID);
});
The parameters your action accepts should represent what your action does/is allowed to do. If a client should not be able to update a related entity, then the class you bind the request body to, should not have that entity available. Use a view model, essentially:
public class CommodityRequest
{
// all properties you want editable
// exclude `CommodityMaterial` obviously
}
Then:
public IActionResult Update(CommodityRequest model)

Deep cloning an object with Automapper, can't exclude Id property on objects in a List<abstractBaseClass> property

I have the following classes:
public abstract class Question : IQuestion
{
[Key]
public int Id { get; set; }
// Some other base properties here
}
public class TextQuestion : Question
{
// Some class-specific properties here
}
And a class like this:
public class SomeCompositeClass
{
[Key]
public int Id { get; set; }
// Some properties go here ...
public virtual List<Question> Questions { get; set; }
}
I want to create a deep clone of the SomeCompositeClass, using Automapper (please don't suggest ICloneable), but without all the IDs, because I will be inserting it in the database, which I access with EntityFramework, repository pattern.
Naturally, I create a mapping:
Mapper.CreateMap<SomeCompositeClass, SomeCompositeClass>().ForMember(rec => rec.Id, opt => opt.Ignore())
and this works out great for the SomeCompositeClass.
But I have a problem doing the same thing for the Questions property! The problem comes from the base class in the List being abstract, not because the list itself is virtual, I have already ruled this out.
If I create a Mapper.CreateMap<Question, Question>() or Mapper.CreateMap<IQuestion, IQuestion>() mapping, the code throws an exception at runtime, complaining that it cannot create an instance of an abstract (Question) object.
I have tried Mapper.CreateMap<List<Question>, List<Question>>(), but this just gives me an empty Questions list at runtime.
I have tried creating question-specific mappings (TextQuestion to TextQuestion), but they don't kick in, because the objects in the Questions property are wrapped in EF's DynamicProxy classes.
What can I do, to exclude the Id from the inheritors of my abstract base Question class, during the Mapper.Map(...)?
I solved it the following way:
First, I updated to Automapper 4.1.1. Then:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Question, Question>()
.Include<TextBoxQuestion, TextBoxQuestion>()
// Supposedly inheritance mapping?
.ForMember(rec => rec.Id, opt => opt.Ignore());
cfg.CreateMap<TextBoxQuestion, TextBoxQuestion>()
// But either I don't understand inheritance mapping or it doesn't work, soI have to do that too
.ForMember(rec => rec.Id, opt => opt.Ignore());
cfg.CreateMap<SomeCompositeClass, SomeCompositeClass>()
.ForMember(rec => rec.Id, opt => opt.Ignore())
}
...
Mapper.Map(source, destination);
and it works...
So I think what I was mostly missing was the .Include part, which tells Automapper to look for the most derived class.

Table-per-Hierarchy and Many to Many relationship

this may seem very easy to solve, but the complication I'm having is that I have a table per hierarchy to store all of the entities and I'm not able to create the relationships over the same table. Here is what I have in the DB and classes:
I have just one table named BaseObject with an ID, name and type. I will create two classes for those entities stored in there. Master and Component. The type column is the discriminator. I have another table to store the relationships between both: A master can have many components and a component also can have many other components.
This is the code I have for the classes:
public partial class BaseObject
{
public BaseObject()
{
}
public System.Guid ID { get; set; }
public string Name { get; set; }
public Nullable<int> Type { get; set; }
}
public class MasterObject : BaseObject
{
public virtual ICollection<ComponentObject> Components { get; set; }
public MasterObject()
{
this.Components = new List<ComponentObject>();
}
}
public class ComponentObject : BaseObject
{
public virtual ICollection<MasterObject> MasterObjects { get; set; }
public ComponentObject()
{
this.MasterObjects = new List<MasterObject>();
}
}
And these are the mappings:
public class BaseObjectMap : EntityTypeConfiguration<BaseObject>
{
public BaseObjectMap()
{
// Primary Key
this.HasKey(t => t.ID);
// Table & Column Mappings
this.ToTable("BaseObject");
this.Property(t => t.ID).HasColumnName("ID");
this.Property(t => t.Name).HasColumnName("Name");
//configure the inheritance in here
this.Map<MasterObject>(m => m.Requires("Type").HasValue(1));
this.Map<ComponentObject>(m => m.Requires("Type").HasValue(2));
}
}
public class MasterObjectMap : EntityTypeConfiguration<MasterObject>
{
public MasterObjectMap()
{
this.HasMany<ComponentObject>(t => t.Components)
.WithMany(t => t.MasterObjects)
.Map(c =>
{
c.ToTable("ObjectComponents");
c.MapLeftKey("ComponentObjectID");
c.MapRightKey("BaseObjectID");
});
}
}
public class ComponentObjectMap : EntityTypeConfiguration<ComponentObject>
{
public ComponentObjectMap()
{
this.HasMany<MasterObject>(t => t.MasterObjects)
.WithMany(t => t.Components)
.Map(m =>
{
m.ToTable("ObjectComponents");
m.MapLeftKey("BaseObjectID");
m.MapRightKey("ComponentObjectID");
});
}
}
The thing is that when quering the DB, I can get a Master by accessing the DBSet Masters, but the Component collection always gives a non-sense exception saying that "Problem in mapping fragments starting at line 6:Condition member 'BaseObject.Type' with a condition other than 'IsNull=False' is mapped. Either remove the condition on BaseObject.Type or remove it from the mapping."
I don't understand what's happening. Of course if the classes where pointing each to a table, this would be very easy, but I suspect that is the root of my problem.
Also, I'm just starting with EF. I wanted to create the classes based on my existing DB that I wouldn't like to modify at all. Unless it's really needed. Please guide me if what I'm trying to do is right or wrong, or what should I do first to fully implement EF on this project that is currently using NHibernate.
Any help here? Thanks
Finally I found the issue thanks to this answer: EF4.1 Exception creating Database with table-per-hierarchy inheritance
The problem is in the mapping of the Base class. When using a base class and a discriminator to get the children classes, the discriminator MUST NOT BE a property in neither instance. Just removed the discriminator as property and it worked fine. In my example, column 'Type' is the discriminator.
Hope this helps to someone else.

TPH Simple Config / Discriminator issue

I'm attempting a little EF with code first - and I can't figure out where I have gone wrong with the is simply example of my own. I'm just out of ideas, and would like to nail down where I'm going wrong...
First the simply POCO class representing a location - a location can be a RadioStation, or a Merchant. I haven't added additional fields (which will come later), so right now it's just a TPH in as simple config as I can make it.
namespace EFDataClasses.Entities
{
public class RadioStation : Location
{
public RadioStation()
{
}
}
public class Merchant : Location
{
public Merchant()
{
}
}
public class Location
{
public Location()
{
}
public int Loc_ID { get; set; }
public string Loc_Code { get; set; }
public string Loc_Name { get; set; }
public string Loc_Type {get;set;}
}
}
then the config class:
namespace EFDataClasses.Mapping
{
public class LocationMap : EntityTypeConfiguration<Location>
{
public LocationMap()
{
// Primary Key
this.HasKey(t => t.Loc_ID);
// Properties
this.Property(t => t.Loc_ID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// Properties
this.Property(t => t.Loc_Code)
.IsRequired()
.HasMaxLength(50);
this.Property(t => t.Loc_Name)
.IsRequired()
.HasMaxLength(50);
this.Property(t => t.Loc_ID).HasColumnName("Loc_ID");
this.Property(t => t.Loc_Code).HasColumnName("Loc_Code");
this.Property(t => t.Loc_Name).HasColumnName("Loc_Name");
//my discriminator property
this.Property(t => t.Loc_Type).HasColumnName("Loc_Type").HasColumnType("varchar").HasMaxLength(50).IsRequired();
// Table & Column Mappings
this.Map(m =>
{
m.ToTable("Location");
m.Requires("Loc_Type").HasValue("Location");
}
)
.Map<RadioStation>(m =>
{
m.ToTable("Location");
m.Requires("Loc_Type").HasValue("RadioStation");
}
)
.Map<Merchant>(m =>
{
m.ToTable("Location");
m.Requires("Loc_Type").HasValue("Merchant");
}
)
;
}
}
}
here is the context:
namespace EFDataClasses
{
public class MyContext : DbContext
{
static MyContext()
{
Database.SetInitializer<MyContext>(new DropCreateDatabaseAlways<MyContext>());
}
public DbSet<EFDataClasses.Entities.Location> Locations {get; set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new LocationMap());
}
}
}
and finally the program class which attempts to add radio station..
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace EFConsole
{
class Program
{
static void Main(string[] args)
{
var db = new EFDataClasses.MyContext();
db.Locations.Add(new EFDataClasses.Entities.RadioStation() { Loc_Name = "Radio Station Name 1", Loc_Code = "RD1" });
int chngs = db.SaveChanges();
System.Diagnostics.Debugger.Break();
}
}
}
The error that I'm getting is a validation error on Loc_Type saying that it's a required field. My impression here is that EF would fill that in when I select the appropriate type - and all my reading supports that.
If I do add the appropriate location type - EF give me another error....
arggghhh!
In the end I would like to make Location abstract but does that mean I can drop the hasvalue("Location")?
I would like to move on here, but I'm curious where I have done wrong. thanks!
The problem is that when you use a column as a discriminator for TPH mapping you cannot also map that column to a property in your class. This is because EF is now controlling the value of that column based on the type of .NET class. So you should remove this line that does the mapping of the Loc_Type property to the Location column:
// Remove this line:
this.Property(t => t.Loc_Type).HasColumnName("Loc_Type").HasColumnType("varchar").HasMaxLength(50).IsRequired();
If you need (or want) to specify an explicit column type, size, etc for the discriminator column then you can do that in the Map call. For example:
Map(m =>
{
m.ToTable("Location");
m.Requires("Loc_Type")
.HasValue("Location")
.HasColumnType("varchar")
.HasMaxLength(50)
.IsRequired();
}
You don't need to have any property in the class representing the location type. If you do want to get a string value equivalent of Loc_Type then you can use something like this in Location:
public virtual string Loc_Type
{
get { return "Location"; }
}
Then override it in the other classes. For example:
public override string Loc_Type
{
get { return "RadioStation"; }
}
The rest of the code looks fine.
I think you're over-complicating things a bit, and that's dangerous around CF.
With code-first, you have to stick to 'tried and tested' patterns that work, otherwise mixing things and constructs will get you into trouble soon enough.
First, why do you need to map and have Loc_Type at all, I mean in the class model? That's superfluous, you'll get that with your 'class' type really, your class type is the discriminator and vice versa.
And that's what the error is saying pretty much I think, either supply or 'drop the column from mapping' or something.
So just drop that from the 'Location' and it works w/o problems. Also you don't really need to specify discriminator most of the time unless you want to have your Db makes sense and used by other sides etc. which is ok though.
If you want you can make it abstract, Location - and that's normally done, and I think you can drop it then from the mappings, only 'real implemented' classes should be mapped as only those could be 'instantiated'.
And lastly, TPH is for various reasons not the most 'fortunate' solution, and the most error prone of all I think, TPT, TPC all work much more 'fluid'. You cannot have your 'child classes' 'properties be non null, etc. etc. there are posts on that.
hope this helps.
EDIT: if you'd like to control the 'hidden' discriminator I think you could try and manually adjust the migration file for that column specifically, to set it's size etc. If that is important (code first should pick the right values there I guess). If you don't change anything significant to the sense of how things work that should work.