EF Core fluent mapping to inner object properties - c#

I have a class that contains some properties.
For some architectural reasons, I have an instance of another objet into my class.
Simple example
public class MyEntity {
public MySubEntity SubEntity {get; set;}
}
For this, I create fluent mapping like :
builder.ToTable(MyEntity.CONST_TABLE_NAME);
builder.HasKey(m => m.Id);
builder.Property(m => m.Column1).IsRequired();
builder.Property(m => m.SubEntity.Column2).IsRequired();
I cannot integrate all my subEntity properties into my main entity (my subEntity has its own intelligence). I just want to map my subentity properties, which is NOT stored in a separated table, to myEntity table.
The last line throw an exception :
The expression 'm => m.SubEntity.Column2' is not a valid property expression. The expression should represent a property access: 't => t.MyProperty'.
How can I perform such mapping ?

EF Core doesn't support this type of mapping for now. It will not be supported in EF Core 1.0 RTM (see my github issue : https://github.com/aspnet/Home/issues/1330)
As I described in my github issue, I figured out 2 solutions :
1) Create a derived class from my model, specialy designed for EF, and expose all properties as simple. It will need more mapping when insert/update and retrieve from Db. We don't choose this option
2) Create proxy properties. In my example, this is like :
public class MyEntity {
private MySubEntity SubEntity {get; set;}
public string SubEntityValue
{
get
{
return SubEntity.Value;
}
set
{
SubEntity.Value = value;
}
}
This seems to be the best solution (we choose this one).

Related

Why is EF Core treating my non-decorated class as an entity?

I have a simple class as follows:
public class NonEntity
{
// some properties
}
I'm getting runtime errors that point to Entity Framework Core thinking this is a POCO (entity) class. For example:
The entity type 'NonEntity' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'
However, there is no DbSet<NonEntity> property in my DB context, and there are no attributes on the NonEntity class to suggest anything other than this being a standard class having nothing to do with my database. What would cause EF Core to think otherwise?
Turns out this was happening because I was referencing the class inside of another class that is an entity:
public class MyEntity
{
public int Id { get; set; }
public List<NonEntity> NonEntities
{
get
{
// some logic that converts entities to non-entities and returns them
}
}
}
Adding the NotMapped attribute to the property definition resolved it:
[NotMapped]
public List<NonEntity> NonEntities
By default, EF tries to map properties to the database. The NotMapped attribute overrides this behavior.

How to implement non-primitive FK correctly in EF Core via fluent

I've trying to create a new foreign key in entity framework core 6 but I am getting the following error?
The types of the properties specified for the foreign key {'RoleId' : RoleId} on entity type 'SomeEntity' do not match the
types of the properties in the principal key {'Id' : Guid} on entity
type 'DbsRole'. Provide properties that use the same types in the same
order.
How can I fix this via Fluent API without adding navigation properties to my entities as I am following DDD principles (bounded contexts etc)?
I thought the conversion handler would be sufficient but obviously not.
RoleId.cs
public record RoleId(Guid Value);
SomeEntity.cs
public class SomeEntity : Entity<Guid>, IAggregateRoot
{
public Guid Id {get; private set;} = null!;
public string Name { get; private set; } = null!;
public RoleId RoleId { get; private set; } = null!;
}
MyContext.cs
public partial class MyContext : IdentityDbContext<MyUser, MyRole, Guid>
{
public virtual DbSet<SomeEntity> SomeEntity { get; set; } = default!;
//... snip...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
_ = modelBuilder.Ignore<RoleId>();
_ = modelBuilder.Entity<SomeEntity>(entity =>
{
_ = entity.HasKey(e => e.Id);
_ = entity.Property(e => e.Name)
.IsRequired();
_ = entity.HasIndex(e => e.Name);
_ = entity.Property(e => e.RoleId)
.IsRequired()
.HasConversion(x => x.Value, x => new RoleId(x));
_ = entity.HasOne<MyRole>()
.WithMany()
.IsRequired()
.HasForeignKey(p => p.RoleId);
});
}
}
MyUser.cs
public class MyUser : IdentityUser<Guid>
{
//... snip...
}
MyRole.cs
public class MyRole : IdentityRole<Guid>
{
// ...snip...
}
Regards
Kyle
How can I fix this via Fluent API without adding navigation properties to my entities as I am following DDD principles (bounded contexts etc)?
You can't (at least currently - up to EF Core 6.0 inclusive). Navigation properties have nothing to do with the problem, which is the type of the PK and FK properties. The error message is quite clear of what is expected
Provide properties that use the same types...
You may consider it EF Core limitation/shortcoming, but it is what it is, and you must follow EF Core data model rules if you want to use it for persisting your domain model.
Which in general means you shouldn't be using the domain model as data model directly. Bounded contexts, value objects, encapsulation - these are not natural for data models. With small exceptions, EF Core context represents a database, DbSet is a table, entity is a record in that table, and navigation properties are relationships. So the best would be if you create separate models and map between the two where needed. Yes, it requires additional efforts, but that's the only way you can follow the differences between the two models requirements.
But back on the concrete issue. EF Core requires both properties to have one and the same type. This means you either have to use Guid type for SomeEntity.RoleId, or use RoleId type for MyRole.Id. However the second is not possible with identity model, since both user, role and related things are constrained to have one and the same type of key (the last generic type argument, in your case Guid). So I'm afraid that if you want to use the domain model as data model directly, the only option currently in this particular case is to make SomeEntity.RoleId type Guid.

Why EF6 not support ` ignore ` the property of the entity Property(complex property) Fluent API?

if the entity as
public class AddressDetail
{
public string Country{get;set;}
}
public class Order
{
public AddressDetail AddressDetail{get;set;}
}
How ignore the Oreder.AddressDetail.Country property by Fluent API Not [NotMap]?
I found the solution for EF6,but I don't know Why Before EF6 have the function,EF6 don't have the function?
For EF5 and older:
In the DbContext.OnModelCreating override for your context:
modelBuilder.Entity<Product>().Ignore(p => p.AddressDetails.Country);
For EF6: You're out of luck. See Mrchief's answer.
I understand this exception that only plain property expressions are allowed, so if you want to ignore the property of a property, you have to do it on the type of the outer property:
modelBuilder.Types<WhateverTheTypeOfResponseIs>()
.Configure(c => c.Ignore(r => r.MobilePhone));
Though, i guess the proper syntax for EF6 would be:
modelBuilder.Entity<WhateverTheTypeOfResponseIs>()
.Ignore(r => r.MobilePhone);

EF 5 Code First - Why is explicitly loaded data not accessible from my model?

I have the following two model objects which have a many-to-many relationship:
public class StaffMember
{
public Guid StaffMemberKey {get; set;}
// lots of other properties that aren't relevant
public ICollection<Case> Cases {get; set;}
}
public class Case
{
public int CaseKey {get; set;}
// lots of other properties that aren't relevant
public ICollection<StaffMember> Staff {get; set;}
}
The mapping for the many-to-many relationship is handled in the configuration for the Case entity:
public class CaseMapping : EntityTypeConfiguration<Case>
{
public CaseMapping()
{
// other property and relationship mappings
// Many-to-Many mapping with Staff Members
HasMany(c => c.Staff)
.WithMany(staffMember => staffMember.Cases)
.Map(m =>
{
m.ToTable("Cases_StaffMembers", "dbo");
m.MapLeftKey("CaseKey");
m.MapRightKey("StaffMemberKey");
});
}
}
Everything is working great in terms of being able to query against this relationship, add, delete, etc. However, when trying to explicitly load and filter staff members for a case, as described here, no data is being loaded in to the appropriate collection of related entities.
Here is an example of what I'm attempting do:
var staffMemberKey = Guid.Parse("...");
var caseKey = 5;
using (var context = new CodeFirstContext())
{
var selectedCase = context.Cases.Find(caseKey);
context.Entry(selectedCase).Collection(c => c.Staff).Query().Where(sm => sm.StaffMemberKey == staffMemberKey).Load();
}
I would expect that selectedCase.Staff would contain the staff member that was loaded, but it remains null. If I call ToList() instead of Load when querying for the related data, the resulting list does contain the correct staff member entity. If I simply call context.Entry(selectedCase).Collection(c => c.Staff).Load();, then the data is loaded as expected. Is there something I'm missing? What gives?
As a final note, I have lazy loading and proxy creation disabled for my context, in case that makes any difference in this scenario.
When you call Query(), it returns an IQueryable that gives the entities that would be in that property -- it is not designed to be used to update the property. It is basically a "shortcut" for:
ctx.Staff.Where(staff => staff.Case.Id == caseKey);
Load() will load entities into your context, as if you had called ToList() but without returning anything. It works on any IQueryable, and does not capture anything related to the Entry().

Entity Framework + AutoMapper ( Entity to DTO and DTO to Entity )

I've got some problems using EF with AutoMapper. =/
for example :
I've got 2 related entities ( Customers and Orders )
and they're DTO classes :
class CustomerDTO
{
public string CustomerID {get;set;}
public string CustomerName {get;set;}
public IList< OrderDTO > Orders {get;set;}
}
class OrderDTO
{
public string OrderID {get;set;}
public string OrderDetails {get;set;}
public CustomerDTO Customers {get;set;}
}
//when mapping Entity to DTO the code works
Customers cust = getCustomer(id);
Mapper.CreateMap< Customers, CustomerDTO >();
Mapper.CreateMap< Orders, OrderDTO >();
CustomerDTO custDTO = Mapper.Map(cust);
//but when i try to map back from DTO to Entity it fails with AutoMapperMappingException.
Mapper.Reset();
Mapper.CreateMap< CustomerDTO , Customers >();
Mapper.CreateMap< OrderDTO , Orders >();
Customers customerModel = Mapper.Map< CustomerDTO ,Customers >(custDTO); // exception is thrown here
Am I doing something wrong?
Thanks in Advance !
The problem I had was related to updates to EntityCollection references. AutoMapper creates a new instance of the relation when mapping from the DTO to the Entity, and that doesn't please the EF.
What solved my problem was configuring AutoMapper to use the destination value for my EntityCollection properties. In your case:
Mapper.CreateMap< CustomerDTO , Customers >().ForMember(c => c.Orders, o => o.UseDestinationValue());
That way AM will not create a new EntityCollection instance, and will use that wich came with the original Customer entity.
I'm still working for a way to automate this, but for now it solves my problem.
Try mapping to an existing object:
entity = Mapper.Map<MyDTO, NyEntity>(dto, entity);
And keep the Ignore()'s in place.
http://groups.google.com/group/automapper-users/browse_thread/thread/24a90f22323a27bc?fwc=1&pli=1
Your problem is because Automapper loses the EntityKey associated with the record. As the EntityFramework does not by default handle POCO's (Plain Old CLR Object)
Jay Zimmerman has a good example here of how to handle this from is. gd /4NIcj
Also from Jaroslaw Kowalski (part of the EF team I believe ) has this example for using POCO's within EF, which may translate well to use with Automapper (I've not yet had a chance to try it) : http://blogs.msdn.com/jkowalski/archive/2008/09/09/persistence-ignorance-poco-adapter-for-entity-framework-v1.aspx
I'm not sure what your problem is, but - when i wanted to use LINQToEntities (switched to NHibernate),
i managed to use automapper with success.
Take a look at code:
public class SimpleMapper<TFrom, TTo>
{
public static TTo Map(TFrom fromModel)
{
Mapper.CreateMap<TFrom, TTo>();
return Mapper.Map<TFrom, TTo>(fromModel);
}
public static IList<TTo> MapList(IList<TFrom> fromModel)
{
Mapper.CreateMap<TFrom, TTo>();
return Mapper.Map<IList<TFrom>, IList<TTo>>(fromModel);
}
}
public class RepositoryBase<TModel, TLINQModel>
{
public IList<TModel> Map<TCustom>(IList<TCustom> model)
{
return SimpleMapper<TCustom, TModel>.MapList(model);
}
public TModel Map(TLINQModel model)
{
return SimpleMapper<TLINQModel, TModel>.Map(model);
}
public TLINQModel Map(TModel model)
{
return SimpleMapper<TModel, TLINQModel>.Map(model);
}
public IList<TModel> Map(IList<TLINQModel> model)
{
return SimpleMapper<TLINQModel, TModel>.MapList(model);
}
public IList<TLINQModel> Map(IList<TModel> model)
{
return SimpleMapper<TModel, TLINQModel>.MapList(model);
}
}
It's quite cryptic, always recreates mappings, but it worked. I hope it helps somehow. :)
Now, with new version of AutoMapper, the recommended way is using Queryable-Extensions:
When using an ORM such as NHibernate or Entity Framework with
AutoMapper's standard Mapper.Map functions, you may notice that the
ORM will query all the fields of all the objects within a graph when
AutoMapper is attempting to map the results to a destination type.
If your ORM exposes IQueryables, you can use AutoMapper's
QueryableExtensions helper methods to address this key pain.
The .ProjectTo() will tell AutoMapper's mapping engine
to emit a select clause to the IQueryable that will inform entity
framework that it only needs to query the Name column of the Item
table, same as if you manually projected your IQueryable to an
OrderLineDTO with a Select clause.
Create a mapping:
Mapper.CreateMap<Customer, CustomerDto>();
And project query to dto:
var customerDto =
session.Query<Customer>().Where(customer => customer.Id == id)
.Project().To<CustomerDto>()
.Single();
AutoMapper is very expressive when it comes to mapping error. read the exception message carefully.
another important thing is to remember to call Mapper.AssertConfigurationIsValid(); after creating the mappings. it gives an error if the mapping is wrong, thus preventing an exception later in the application runtime.
You should ignore mapping of some entity properties like so:
Mapper.CreateMap<CustomerDto, Customer>()
.ForMember(dest => dest.EntityKey, opt => opt.Ignore())
.ForMember(dest => dest.Licenses, opt => opt.Ignore())
.ForMember(dest => dest.AccessCodes, opt => opt.Ignore());
If you examine the message from the exception thrown by Automapper, you should see the entity properties that cannot be mapped and ignore them as above.
As you can read here you need to do the following
You can update entities with AutoMapper. Here's how: pass both the DTO and the entity object to AutoMapper's Map method. That's what this code does:
custExisting = Mapper.Map(Of CustomerDTO, Customer)(custDTO, custExisting)
Also beware of mapping issues like the one described here
These tips worked for me.

Categories

Resources