Entity Framework Inheritance with navigation property - c#

I'm using Entity Framework with a code-first approach
But I get a few errors:
User: FromRole: NavigationProperty 'User' is not valid. Type 'SoteAccount' of FromRole 'User_SoteAccounts_Target' in AssociationType 'User_SoteAccounts' must exactly match with the type 'AllegroAccount' on which this NavigationProperty is declared on.
AllegroAccount_Template_Source: : Multiplicity is not valid in Role 'AllegroAccount_Template_Source' in relationship 'AllegroAccount_Template'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be ''.
SoteAccount_Template_Source: : Multiplicity is not valid in Role 'SoteAccount_Template_Source' in relationship 'SoteAccount_Template'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be ''.
Is it even possible to inherit a class with reference ?
Here are the classes and onModelCreating
[Table("AllegroAccounts")]
public class AllegroAccount : ShopAccountBase
{
public string AllegroLogin { get; set; }
public string AllegroPassword { get; set; }
public string AllegoWebApiKey { get; set; }
public int CountryCode { get; set; }
}
public class ShopAccountBase : AccountBase
{
public int TemplateForeignKey { get; set; }
[ForeignKey("TemplateForeignKey")]
public Template Template { get; set; }
}
public abstract class AccountBase
{
[Key]
public int AccountBaseId { get; set; }
public bool IsActive { get; set; }
public int UserForeignKey { get; set; }
[ForeignKey("UserForeignKey")]
public virtual User User { get; set; }
public bool DaysCountActive { get; set; }
public int DaysCount { get; set; }
}
public class Template
{
public Template()
{
AdditionalServices = new AdditionalServices();
BasicServices = new BasicServices();
TemplatePackages = new ObservableCollection<TemplatePackage>();
}
[Key]
public int TemplateID { get; set; }
public string TemplateName { get; set; }
public TemplateKind? TemplateKind { get; set; }
public CourierFirm? CourierFirm { get; set; }
public int Used { get; set; }
public virtual ICollection<TemplatePackage> TemplatePackages { get; set; }
public string ExternalNumber { get; set; }
public string MPKNumber { get; set; }
public AdditionalServices AdditionalServices { get; set; }
public BasicServices BasicServices { get; set; }
public string Description { get; set; }
[Column(TypeName = "datetime")]
public DateTime? CreationDate { get; set; }
}
public class User
{
public User()
{
DefaultReturnAddress = new Address( );
DefaultSendingAddress = new Address( );
PersonInfoSending = new PersonInfo( );
PersonInfoReturning = new PersonInfo( );
AdditionalServices = new AdditionalServices( );
WayBillLabel = new WaybillLabel( );
Settings = new UserSettings( );
AllegroAccounts = new ObservableCollection<AllegroAccount>();
InpostAccounts = new ObservableCollection<InpostAccount>();
TbaAccounts = new ObservableCollection<TbaAccount>();
TruckerAccounts = new ObservableCollection<TruckerAccount>();
}
[Key]
public int UserId { get; set; }
public byte[] Password { get; set; }
public string Login { get; set; }
public Address DefaultReturnAddress { get; set; }
public Address DefaultSendingAddress { get; set; }
public PersonInfo PersonInfoSending { get; set; }
public PersonInfo PersonInfoReturning { get; set; }
public string MPKnumReturn { get; set; }
public string MPKnumSending { get; set; }
public AdditionalServices AdditionalServices { get; set; }
public float MaxLength { get; set; }
public float MaxWidth { get; set; }
public float MaxHeight { get; set; }
public float MaxWeight { get; set; }
public int FileTemplateId { get; set; }
public string CollectiveShipmentFilePath { get; set; }
private PermissionFlags _permissions;
public PermissionFlags Permissions
{
get { return _permissions; }
set
{
if (_permissions.HasFlag(value)) { _permissions &= ~value; }
else {
_permissions |= value;
}
}
}
public TemplatingMethod TemplatingMethod { get; set; }
public UserSettings Settings { get; set; }
public WaybillLabel WayBillLabel { get; }
public ICollection<AllegroAccount> AllegroAccounts { get; set; }
public ICollection<SoteAccount> SoteAccounts { get; set; }
public ICollection<InpostAccount> InpostAccounts { get; set; }
public ICollection<TruckerAccount> TruckerAccounts { get; set; }
public ICollection<TbaAccount> TbaAccounts { get; set; }
// this is the right property to use for modifying the collection
public ICollection<string> AvailableMpksCollection { get; set; }
// this is computed property for Entity Framework only, because it cannot store a collection of primitive type
public string AvailableMpksString
{
get { return AvailableMpksCollection != null ? string.Join(",", AvailableMpksCollection) : null; }
set {
AvailableMpksCollection = !string.IsNullOrEmpty(value) ? value.Split(',').ToList( ) : new List<string>( );
}
}
}
modelBuilder.Entity<AllegroAccount>().HasOptional(account => account.Template).WithOptionalDependent();
modelBuilder.Entity<User>()
.HasMany<AllegroAccount>(u => u.AllegroAccounts)
.WithOptional(acc => acc.User)
.HasForeignKey(acc => acc.UserForeignKey);
modelBuilder.Entity<SoteAccount>().HasOptional(account => account.Template).WithOptionalDependent();
modelBuilder.Entity<User>()
.HasMany<SoteAccount>(u => u.SoteAccounts)
.WithOptional(acc => acc.User)
.HasForeignKey(acc => acc.UserForeignKey);
Does anyone know if it's possible or should I keep it flat and don't inherit it like that? I'm asking because that inheritance would fit nicely with my generic repository model

This is likely because you are defining [ForeignKey] attributes AND configuring the foreign key in the fluent configuration.
You've defined links between (AllegroAccount and User) and (SoteAccount and User) in the fluent configuration, when your AccountBase already has this defined using the [ForeignKey].
The same thing is most likely true for your Template links - the relationship is defined at the ShopAccountBase level by the [ForeignKey] attribute - you don't need to redefine it for the inherited classes in the fluent config.
Try removing all of your modelBuilder fluent configuration entries - it should still work by inheriting the relationships.

Related

Return all derived models in EF Core (TPH )

I have a task which requires me to return all models from a table using inheritance (TPH).
I have a model class called WorkflowInstance and a derived class CustomWorkflowInstance (which has a string property). There is a discriminator of course.
I want to know of a way where I can return all the elements without considering the discriminator
public class WorkflowInstance : Entity, ITenantScope, ICorrelationScope
{
public WorkflowInstance();
public SimpleStack<ActivityScope> Scopes { get; set; }
public SimpleStack<ScheduledActivity> ScheduledActivities { get; set; }
public WorkflowFault? Fault { get; set; }
public HashSet<BlockingActivity> BlockingActivities { get; set; }
public IDictionary<string, IDictionary<string, object?>> ActivityData { get; set; }
public WorkflowOutputReference? Output { get; set; }
public WorkflowInputReference? Input { get; set; }
public Variables Variables { get; set; }
public Instant? FaultedAt { get; set; }
public Instant? CancelledAt { get; set; }
public Instant? FinishedAt { get; set; }
public Instant? LastExecutedAt { get; set; }
public Instant CreatedAt { get; set; }
public string? Name { get; set; }
public string? ContextId { get; set; }
public string? ContextType { get; set; }
public string CorrelationId { get; set; }
public WorkflowStatus WorkflowStatus { get; set; }
public int Version { get; set; }
public string? TenantId { get; set; }
public string DefinitionId { get; set; }
public ScheduledActivity? CurrentActivity { get; set; }
public string? LastExecutedActivityId { get; set; }
}
public class CustomWorkflowInstance : WorkflowInstance
{
public Guid UserId { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<WorkflowInstance>()
.HasDiscriminator<int>("Discriminator")
.HasValue(0)
.HasValue<WorkflowInstance>(0)
.HasValue<CustomWorkflowInstance>(1);}
I want to find a way to query the table as it is meaning adding where clause FinishedAt > etc (the issue is that UserId exist only in derived class but all the data is in base class where discriminator always equals 0)
so by doing select * from WorkflowInstanceTABLE where Used="xx" it automatically adds the where discriminator = 1 (because I wrote _dbContext.CustomWorkflowInstance which contains the userId in question.

Automapper not working on nested objects

I currently working with .net core 2.1 and try to use automapper for nested objects to convert model to dto and dto to model. When every field is mapped correctly issue appears with relationship mapping.
Models
public class DropdownValue
{
public int Id { get; set; }
public string Value { get; set; }
public int PropertyId { get; set; }
public Property Property { get; set; }
}
public class Property
{
public int Id { get; set; }
public string Title { get; set; }
public ValueTypes ValueType { get; set; }
public InputTypes InputType { get; set; }
public List<DropdownValue> DropdownValues { get; set; }
}
Dtos
public class DropdownValueDto
{
public int Id { get; set; }
public string Value { get; set; }
public PropertyDto Property { get; set; }
}
public class PropertyDto
{
public int Id { get; set; }
public string Title { get; set; }
public InputTypes InputType { get; set; }
public ValueTypes ValueType { get; set; }
}
Mapper
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Property, PropertyDto>();
CreateMap<DropdownValue, DropdownValueDto>();
}
}
Usage in handler
_mapper.Map<List<Models.DropdownValue>, List<DropdownValueDto>>(dropdownValues)
I always use automapper mapping tool in .net 4x framework projects but when i develop .net core projects, i always use and recommend mapster mapping tool. It is pretty fast and simple ! Benchmark Results It also solves your problem. You can check the example usage where is below.
First create a mapper class.
public static class Mapper
{
public static void CreateMap()
{
TypeAdapterConfig<Property, PropertyDto>
.NewConfig();
TypeAdapterConfig<DropdownValue, DropdownValueDto>
.NewConfig();
}
}
Initialize in startup
public Startup(IHostingEnvironment env)
{
// other stuffs
// Mapping
Mapper.CreateMap();
}
Usage
dropdownValues.Adapt<List<Models.DropdownValue>, List<DropdownValueDto>>()
//Models
public class DropdownValue
{
public int Id { get; set; }
public string Value { get; set; }
public int PropertyId { get; set; }
public Property Property { get; set; } = new Property();
}
public class Property
{
public int Id { get; set; }
public string Title { get; set; }
public ValueTypes ValueType { get; set; } = new ValueTypes();
public InputTypes InputType { get; set; } = new InputTypes();
public List<DropdownValue> DropdownValues { get; set; } = new List<DropdownValue>();
}
//Dtos
public class DropdownValueDto
{
public int Id { get; set; }
public string Value { get; set; }
public PropertyDto Property { get; set; } = new PropertyDto();
}
public class PropertyDto
{
public int Id { get; set; }
public string Title { get; set; }
public InputTypes InputType { get; set; } = new InputTypes();
public ValueTypes ValueType { get; set; } = new ValueTypes();
}

Automapper many to many mapping confusion

I have many to many relationship tables such as "User & Notification & UserNotification" and their entities, view models also.
There is only a difference between ViewModel and Entity classes. HasRead property is inside NotificationViewModel. How Can I map this entities to view models? I could not achieve this for HasRead property.
What I did so far is,
Mapping Configuration:
CreateMap<Notification, NotificationViewModel>();
CreateMap<User, UserViewModel>().ForMember(dest => dest.Notifications, map => map.MapFrom(src => src.UserNotification.Select(x => x.Notification)));
Notification class:
public class Notification : IEntityBase
{
public Notification()
{
this.UserNotification = new HashSet<UserNotification>();
}
public int Id { get; set; }
public string Header { get; set; }
public string Content { get; set; }
public System.DateTime CreateTime { get; set; }
public bool Status { get; set; }
public virtual ICollection<UserNotification> UserNotification { get; set; }
}
User Class
public class User : IEntityBase
{
public User()
{
this.UserNotification = new HashSet<UserNotification>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public bool Status { get; set; }
public virtual ICollection<UserNotification> UserNotification { get; set; }
}
UserNotification class:
public class UserNotification : IEntityBase
{
public int Id { get; set; }
public int UserId { get; set; }
public int NotificationId { get; set; }
public bool HasRead { get; set; }
public virtual Notification Notification { get; set; }
public virtual User User { get; set; }
}
UserViewModel class
public class UserViewModel : IValidatableObject
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public bool Status { get; set; }
public IList<NotificationViewModel> Notifications { get; set; }
}
NotificationViewModel class
public class NotificationViewModel
{
public int Id { get; set; }
public string Header { get; set; }
public string Content { get; set; }
public System.DateTime CreateTime { get; set; }
public bool Status { get; set; }
public bool HasRead { get; set; } // this is the difference
}
In order to fix up the HasRead, maybe you can utilize the AfterMap(Action<TSource, TDestination> afterFunction) function. It's not as elegant as the rest of automapper, but it might work.
CreateMap<User, UserViewModel>()
.ForMember(dest => dest.Notifications, map => map.MapFrom(src => src.UserNotification.Select(x => x.Notification)))
.AfterMap((src, dest) =>
{
foreach (var notificationVM in dest.Notifications)
{
notificationVM.HasRead = src.UserNotification.Where(x => x.NotificationId == notificationVM.Id).Select(x => x.HasRead).FirstOrDefault();
}
});

The member with identity 'PmData.SafetyRequirement_Assets' does not exist in the metadata collection.\r\nParameter name: identity

I am trying to update an record in my system. Everything on the model saves great, except any of my many to many type relationships on the form. When I get to those in my model it gives me the error. "The member with identity 'PmData.SafetyRequirement_Assets' does not exist in the metadata collection.\r\nParameter name: identity". I've read over some of the other answers but I do not have any triggers on my database, and I've gone through several changes in my model based on other suggestions and it doesn't seem to change anything. The project is in vNext.
Here is my first model
public partial class Asset : DataModel
{
[Required]
[StringLength(64)]
public string Name { get; set; }
[StringLength(256)]
public string Description { get; set; }
[StringLength(1024)]
public string SystemFunction { get; set; }
[StringLength(2048)]
public string Remarks { get; set; }
public bool IsSystem { get; set; }
public bool IsGrouping { get; set; }
[StringLength(128)]
public string FieldTag { get; set; }
[ForeignKey("Parent")]
public int? ParentId { get; set; }
[ForeignKey("Building")]
public int? BuildingId { get; set; }
public bool IsOperable { get; set; }
public bool IsAvailable { get; set; }
public virtual Asset Parent { get; set; }
public virtual Building Building { get; set; }
public virtual ICollection<Asset> Children { get; set; }
public virtual ICollection<DrawingReference> DrawingReferences { get; set; }
public virtual ICollection<SpecReference> SpecReferences { get; set; }
public virtual ICollection<SafetyRequirement> SafetyRequirements { get; set; }
public virtual ICollection<SupportSystem> SupportSystems { get; set; }
}
The model for one the other table with a many to many.
public partial class SafetyRequirement : DataModel
{
[StringLength(256)]
[Required]
public string Name { get; set; }
[StringLength(2048)]
public string SafetyFunction { get; set; }
[StringLength(2048)]
public string FunctionalRequirements { get; set; }
[StringLength(2048)]
public string SystemBoundary { get; set; }
[StringLength(255)]
public string Reference { get; set; }
[ForeignKey("QualityLevel")]
public int QualityLevelId { get; set; }
public virtual QualityLevel QualityLevel { get; set; }
public virtual ICollection<Asset> Assets { get; set; }
}
The map for the joining table
modelBuilder.Entity<Asset>().HasMany(t => t.SafetyRequirements)
.WithMany(t => t.Assets)
.Map(m =>
{
m.MapRightKey("SafetyRequirementId");
m.MapLeftKey("AssetId");
m.ToTable("AssetSafetyRequirement");
});
Finally here's the area that it fails...
public virtual void SaveAsync(TEntity model)
{
Task.Run(() =>
{
using (
var dbContext =
(TContext)
Activator.CreateInstance(typeof (TContext),
ConfigOptions == null ? ConfigService.ConnectionString : ConfigOptions.ConnectionString))
{
var dbSet = dbContext.Set<TEntity>();
dbSet.Attach(model);
dbContext.Entry(model).State = EntityState.Modified;
dbContext.SaveChanges();
}
});
}
Any information or pointers would be greatly appreciated.
You're trying to use both Fluent API and Data Annotations to define the relationships between your tables. Remove one or the other.

EntityFramework code first keys

I have following tables: User, UserGroups, and UserInGroups. You can see them on picture below. When i call User i want to be able to get Groups that user is in (UserInGroups). I am reading materials about EntityFramework but i am unable to make connections in code to to that, what am i missing? Do i need to connect them onModelCreating?
Currently i am not getting any data from UserInGroup or UserGroups.
DB looks like this
My classes look like this:
public class User : BaseEntity
{
public int RoleId { get; set; }
public Role Role { get; set; }
public UserGroup UserGroup { get; set; }
public UserInGroup UserInGroup { get; set; }
}
public class UserGroup : BaseEntity
{
public UserGroup()
{
Users = new List<User>();
UserInGroups = new List<UserInGroup>();
}
[StringLength(255)]
public string Name { get; set; }
public string KeyName { get; set; }
public List<User> Users { get; set; }
public List<UserInGroup> UserInGroups { get; set; }
}
public class UserInGroup : BaseEntity
{
public UserInGroup()
{
Users = new List<User>();
UserGroups = new List<UserGroup>();
}
public int UserId { get; set; }
public User User { get; set; }
public int UserGroupId { get; set; }
public UserGroup UserGroup { get; set; }
public List<User> Users { get; set; }
public List<UserGroup> UserGroups { get; set; }
}
DbContext:
public DbSet<GlobalSettings> GlobalSettings { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserGroup> UserGroups { get; set; }
public DbSet<UserInGroup> UsersInGroups { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<GlobalSettings>().Property(x => x.Key).HasColumnAnnotation("Index", new IndexAnnotation(new[] { new IndexAttribute("Index_VariablenName") { IsClustered = false, IsUnique = true } }));
}
public abstract partial class BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
public class User : BaseEntity
{
public string Username { get; set; }
public string Title { get; set; }
public FirstName { get; set; }
public string LasName { get; set; }
public Genders Gender { get; set; }
public string Email { get; set; }
public int RoleId { get; set; }
public string TechUser { get; set; }
public DateTime TechChangeDate { get; set; }
public int TechVersion { get; set; }
public bool IsDeleted { get; set; }
public virtual Role Role { get; set; }
public virtual ICollection<UserInGroup> UserInGroups { get; set; }
}
public class UserInGroup : BaseEntity
{
public int UserId { get; set; }
public int UserGroupId { get; set; }
public string TechUser { get; set; }
public DateTime TechChangeDate { get; set; }
public int TechVersion { get; set; }
public bool IsDeleted { get; set; }
public virtual User User { get; set; }
public virtual UserGroup UserGroup { get; set; }
}
public class UserGroup : BaseEntity
{
public string Name { get; set; }
public string KeyName { get; set; }
public string TechUser { get; set; }
public DateTime TechChangeDate { get; set; }
public int TechVersion { get; set; }
public bool IsDeleted { get; set; }
}
public class Role : BaseEntity
{
public string Name { get; set; }
}
public enum Genders
{
Male = 1,
Female = 2
}
You can use two methods to fill navigation properties. First is lazy-loading and second is explicit specifying of required properties.
For lazy-loading you should declare your navigation properties as virtual:
public class User : BaseEntity
{
public int RoleId { get; set; }
public virtual Role Role { get; set; }
public virtual UserGroup UserGroup { get; set; }
public virtual UserInGroup UserInGroup { get; set; }
}
public class UserGroup : BaseEntity
{
public UserGroup()
{
Users = new HashSet<User>(); // HashSet is more effective than List
UserInGroups = new HashSet<UserInGroup>();
}
[StringLength(255)]
public string Name { get; set; }
public string KeyName { get; set; }
public virtual ICollection<User> Users { get; set; } // ICollection is less restrective
public virtual ICollection<UserInGroup> UserInGroups { get; set; }
}
Now, you can load f.e. single user:
var justUser = dbContext.Users.Single(u => u.Id == 100);
When you need its properties they will by transparently loaded:
foreach (var userInGroup in user.UsersInGroups) // here is second loading
{
. . .
}
The second way is the calling of the Include method to explicit specifying required properties:
var userWithGroups = dbContext.Users
.Include(u => u.UserInGroups) // include single navigation property
.Include(ugs => ugs.Groups.Select(ug => ug.Group)) // include collection navigation property
.Single(u => u.Id == 100); // get the user with specified id and filled specified properties

Categories