Consider this entity:
public class CondRule
{
public virtual decimal Id { get; set; }
public virtual string Name { get; set; }
public virtual CondRuleType RuleType { get; set; }
public virtual string Statement { get; set; }
}
and CondRuleType is:
public class CondRuleType
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
It is obvious that there is a one to one relation between CondRule and CondRuleType entities.
Also I have CondRuleDto:
public class CondRuleDto
{
public decimal Id { get; set; }
public string Name { get; set; }
public CondRuleType RuleType { get; set; }
}
I have mapped CondRule to CondRuleDto using AutoMapper:
Mapper.CreateMap<CondRule, CondRuleDto>();
When I call Session.Get to get CondRule by id and the map the result to CondRuleDto, AutoMapper does not resolve proxies (here RuleType).
Here is my code:
var condRule = Session.Get<CondRule>(id);
var condRuleDto = Mapper.Map<CondRuleDto>(condRule);
When I watch condRuleDto, RuleType property is a NHibernate proxy. I want AutoMapper to map RuleType proxy to a POCO. How to make this work?
PS: I have to mention that when I use query and use automapper's Project, it will result a list with no proxies (I know that Project make this happen. May be I need something like Project to use after Session.Get):
Session.Query<CondRule>().Project().To<CondRuleDto>().ToList()
Casts won't change the underlying object (i.e. your CondRuleType will be still a proxy even if you map its instance to another property of type CondRuleType).
It seems like you need to create a custom mapping where CondRule.RuleType is mapped creating a new instance of CondRuleType.
Related
I have a business need to dynamically select ONLY the properties of a given model that are specified, similar to an OData select clause. I am currently using Mapster's ProjectToType functionality to populate view models from EF Core entities.
Is there any way to tell Mapster to only select a given list of properties in the query that it generates? Or a way to take the full model mapping, and change mappings at runtime in an instance of TypeAdapterConfig to ignore properties that aren't in a given list of properties?
The end solution needs to be generic and work with navigation properties, because it will be applied to all of our entities in the database. We also used DynamicLinq in some cases, not sure if that can be used on top of Mapsters ProjectToType functionality.
Example:
Entities (Some properties omitted for length):
namespace DataAccess.Entities
{
public class Series
{
public Guid Id { get; set; }
public string Description { get; set; }
public long? StackRank { get; set; }
public string EntityId { get; set; }
// Other properties
}
public class Model
{
public Guid Id { get; set; }
public string Description { get; set; }
public string EntityId { get; set; }
public long? StackRank { get; set; }
public Guid SeriesId { get; set; }
public virtual Series Series { get; set; }
// Other properties
}
}
View Models (Some properties omitted for length):
namespace Models
{
public class Model
{
public Guid Id { get; set; }
public string Description { get; set; }
public string EntityId { get; set; }
public long? StackRank { get; set; }
public Guid SeriesId { get; set; }
public virtual Series Series { get; set; }
// Other properties
}
public class Series
{
public Guid Id { get; set; }
public string Description { get; set; }
public long? StackRank { get; set; }
public string EntityId { get; set; }
// Other properties
}
}
Given a rest call to get a list of all Model view models, with this list of properties to include:
var properties = new List<string> {
"Id",
"EntityId"
"Description",
"Series.Id",
"Series.Description",
"Series.EntityId"
}
The results would return some type of dictionary, dynamic, or anonymous object that contained ONLY these properties, and the other properties would not even be included in the final select of the SQL query that gets created.
In the end, I decided to use Arca Artem's suggestion, with a little twist. I used reflection to grab a list of all properties of the model and cache them. After that, I compared the cached properties vs the list of properties to include, and ignored the properties that weren't in both lists. Kinda like this:
var clonedConfig = mapsterInstance.Clone();
clonedConfig.ForType<TSource, TDestination>().Ignore(propertiesToIgnore);
var models = await query.ProjectToType<TDestination>(clonedConfig).ToListAsync();
Maybe not the most elegant solution, but it worked well enough for what I needed. I also set up our json serializer to ignore null values.
I am new to .NET Core and using EF Core 2
My domain objects are all derived from a base class with some audit fields on it that get set on SaveChanges as needed:
(Simplified below)
public abstract class AuditableEntity
{
public DateTime CreatedOn { get; set; }
[ForeignKey("CreatedBy")]
public Guid? CreatedByWebUserId { get; set; }
public WebUser CreatedBy { get; set; }
public DateTime? UpdatedOn { get; set; }
[ForeignKey("UpdatedBy")]
public Guid? UpdatedByWebUserId { get; set; }
public WebUser UpdatedBy { get; set; }
public DateTime? DeletedOn { get; set; }
[ForeignKey("DeletedBy")]
public Guid? DeletedByWebUserId { get; set; }
public WebUser DeletedBy { get; set; }
}
On add-migration, I get the error:
Unable to determine the relationship represented by navigation property
'Address.CreatedBy' of type 'WebUser'. Either manually configure the
relationship, or ignore this property using the '[NotMapped]' attribute or by
using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Address is one of the classes derived from AuditableEntity:
(Simplified below)
public class Address : AuditableEntity
{
public Guid Id { get; set; }
public string Nickname { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3 { get; set; }
public string City { get; set; }
public string StateProvince { get; set; }
public string PostalCode { get; set; }
public string CountryCode { get; set; }
public decimal Latitude { get; set; }
public decimal Longitude { get; set; }
}
However, I have several objects that use the same "agent and timestamp" pair pattern similar to the above that work just fine such as:
public DateTime? VerifiedOn { get; set; }
[ForeignKey("VerifiedBy")]
public Guid? VerifiedByWebUserId { get; set; }
public WebUser VerifiedBy { get; set; }
The error always comes from Address, and if I remove the base class from Address everything works fine (meaning, these fields get successfully applied to my 15+ other domain objects).
The issue seemingly stems from WebUser having a reference to Address:
(Simplified below)
public class WebUser : AuditableEntity
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string Phone1 { get; set; }
public string Phone1Type { get; set; }
public string Phone2 { get; set; }
public string Phone2Type { get; set; }
[ForeignKey("AddressId")]
public Address Address { get; set; }
public Guid? AddressId { get; set; }
}
What is the correct way of creating these references prioritizing keeping the FK constraints (over keeping the ability to navigate)?
The problem is unrelated to the usage of a base class (the same will happen if you remove the base class, but copy its properties to Address class), but the multiple cross references between the two classes.
By convention EF Core tries to automatically "pair" navigation properties of the two entities in order to form a single relationship, which succeeds in most of the cases. However in this case the WebUser has Address type navigation property and Address class has WebUser type navigation property (actually 3).
Since all they have associated FK property via ForeignKey data annotation, EF Core should be able to correctly identify them as different one-to-many relationships, but it doesn't. Not only it fails with the exception in question, but also doesn't create FK relationships for the WebUser.
Everything works correctly if the base class contains only 1 WebUser type of navigation property, so I'm assuming thet unfortunately you are hitting some current EF Core bug.
As a workaround until they fixed it, I would suggest explicitly configuring the problematic relationships using fluent API, by overriding the OnModelCreating and adding the following code:
var auditableEntityTypes = modelBuilder.Model.GetEntityTypes().Where(t => t.ClrType.IsSubclassOf(typeof(AuditableEntity)));
var webUserNavigations = new[] { nameof(AuditableEntity.CreatedBy), nameof(AuditableEntity.DeletedBy), nameof(AuditableEntity.UpdatedBy) };
foreach (var entityType in auditableEntityTypes)
{
modelBuilder.Entity(entityType.ClrType, builder =>
{
foreach (var webUserNavigation in webUserNavigations)
builder.HasOne(typeof(WebUser), webUserNavigation).WithMany();
});
}
i.e. for each entity class that derives from AuditableEntity we explicitly configure the 3 WebUser reference navigation properties to be mapped to 3 separate one-to-many relationships with no inverse collection navigation properties. Once we do that, EF Core has no problem to correctly map the WebUser.Address FK association.
I have a DbContext with ProxyCreationEnabled set to true (actually it's the default value).
As far as I remember, this enables EF to load proxy entities from database, so any change we make to properties are recognized by the change tracker, and we can call SaveChanges() like this:
using (var db = new MyDbContext())
{
var people = db.People.Where(p => p.Status = PersonStatus.New).ToList();
foreach (var person in people)
{
person.Name = "Something";
}
db.SaveChanges();
}
The problem is: why would EF not use the proxy for a specific class, even though ProxyCreationEnabled is true? The class is not sealed, so it should be able to use proxy.
Here is my sample class:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime RegisterDate { get; set; }
public PersonStatus Status { get; set; }
}
To generate proxy for property it should be virtual
public class Person
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual DateTime RegisterDate { get; set; }
public virtual PersonStatus Status { get; set; }
}
To get change tracking proxies, the
basic rule is that your class must be
public, non-abstract or non-sealed.
Your class must also implement public
virtual getters/setters for all
properties that are persisted.
Finally, you must declare collection
based relationship navigation
properties as ICollection<T> only.
They cannot be a concrete
implementation or another interface
that derives from ICollection<T> (a
difference from the Deferred Loading
proxy)
I'm creating a EF5 entity model with the designer (VS2012), and used the EF5 DbContext generator as code generation item.
My model contains an entity deriving from another (not abstract).
So let's say the base entity is called BaseEntity, and the derived entity is DerivedEntity.
Now I see in the generated context class, that there is no
Public DbSet<DerivedEntity> DerivedEntities { get; set; }
defined.
Only
Public DbSet<BaseEntity> BaseEntities { get; set; }
is defined.
Is this normal ? And if yes, how do I query the derived entities in linq ?
I'm used to query like this:
using(var ctx = new EntityContainer)
{
var q = from e in ctx.DerivedEntities <-- but this is now not possible since it doesn't exist
select e;
return q.ToList();
}
Thanks for replying.
EDIT:
As requested, generated classes posted:
public partial class Scheduling
{
public int Id { get; set; }
public string Subject { get; set; }
public System.DateTime BeginDate { get; set; }
public System.DateTime EndDate { get; set; }
}
public partial class TeamScheduling : Scheduling
{
public int TeamId { get; set; }
public Nullable<int> AssignmentId { get; set; }
public virtual Team Team { get; set; }
public virtual Assignment Assignment { get; set; }
}
public partial class EntityContainer : DbContext
{
public EntityContainer()
: base("name=EntityContainer")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Team> Teams { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Assignment> Assignments { get; set; }
public DbSet<ProductType> ProductTypes { get; set; }
public DbSet<AssignmentPreference> AssignmentPreferences { get; set; }
public DbSet<Scheduling> Schedulings { get; set; }
}
As you see, the EntityContainer class does not contain
public DbSet<TeamScheduling> TeamSchedulings { get; set; }
This is expected when you use inheritance the way you have. context.Schedulings contains both Scheduling objects and TeamScheduling objects. You can get the TeamScheduling objects only by asking for context.Schedulings.OfType<TeamScheduling>(). Note that you cannot meaningfully use context.Schedulings.OfType<Scheduling>() to get the others: that will also include the TeamScheduling objects.
You could alternatively try context.Set<TeamScheduling>(), but I'm not entirely sure that will work.
If your intention is to have two tables come up, say a parent Scheduling entity as well as a child TeamScheduling entity that has a foreign key back to the Scheduling entity, consider using a Table-per-Type (TPT) mapping as discussed here.
In essence, you should modify your "OnModelCreating" method to have the following code:
modelBuilder.Entity<TeamScheduling>().ToTable("TeamScheduling");
This explicitly tells EF that you want to have the TeamScheduling subclass to be represented as its own table. Querying it via LINQ would be simple as you would be able to do something like the following:
var teamScheds = context.Set<TeamScheduling>().Where(s => s.Id == 1).FirstOrDefault();
I have the following class
class MCustomer : DomanEntity
{
public MCustomer()
{
}
public virtual iCustomerEntity CustomerDetials { get; set; }
public virtual SolicitationPreferences SolicitationPreferences { get; set; }
}
public interface iCustomerEntity
{
Contact Contact { get; set; }
}
public class PersonEntity: DomanEntity, iCustomerEntity
{
public PersonEntity()
{
Intrests = new List<Intrest>();
Children = new List<PersonEntity>();
}
public virtual Contact Contact { get; set; }
public virtual DateTime BirthDate { get; set; }
public virtual IList<Intrest> Intrests { get; set; }
public virtual PersonEntity Spouse { get; set; }
public virtual IList<PersonEntity> Children { get; set; }
}
When I use fluent NHibernate AutoMapping I receive this error:
NHibernate.MappingException: An association from the table MCustomer refers to an unmapped class: Calyx.Core.Domain.CRM.iCustomerEntity
How do I set up a property in my domain model that has an Interface type?
I don't think, that you can do that.
When you would try to load your MCustomer (session.Load<MCustomer>(id)), NHibernate would only know, that you want to get MCustomer, that has an iCustomerEntity. It would not know which implementation (PersonEntity or CoderEntity?) to use. How would it know which mapping to use to retrieve the data for iCustomerEntity?
https://www.hibernate.org/hib_docs/nhibernate/html/inheritance.html
Its a standard Nhibernate pattern. I'm trying to do the same thing
Looks like a "any" mapping to me. You should look into that. And as far as I can see FNH does not support that yet.