I have a Child entity of Aggregate Entity with one-to-many relation, and in the child, there is a list containing the parent id. the data coming from the front-end is a list of object and if it is no different from what comes to back-end I will do nothing with it. otherwise, I will remove what has been removed and add what has been added to the table.
I am new at EF CORE and I am trying to apply this login in this relation.
if (child.list.SuccessorId == parent.vehicleCategoryId) => ignore;
if(!child.list.contain(parent.vehicleCategoryId)
remove(parent.vehicleCategoryId) => //delete record with vehicleCategoryId
else
add(child)
here is my entities.
public class VehicleCategory : LookupAggregateRoot<VehicleCategory>
{
#region Constructor
private VehicleCategory()
{
_successors = new List<VehicleSuccessorCategory>();
}
#endregion
#region Data
public virtual LocalizedText Name { get; set; }
public virtual long Sequence { get; set; }
private readonly List<VehicleSuccessorCategory> _successors;
public IEnumerable<VehicleSuccessorCategory> Successors
{
get => _successors.AsReadOnly();
set => throw new NotImplementedException();
}
#endregion
#region Behaviour
public void AddSuccessor(VehicleSuccessorCategory entrySuccessorCategory)
{
_successors.Add(entrySuccessorCategory);
}
public void RemoveSuccessor(VehicleSuccessorCategory entrySuccessorCategory)
{
_successors.Remove(entrySuccessorCategory);
}
}
public class VehicleSuccessorCategory : ID365Entity<int>, IEnumerable
{
#region Constructor
public int Id { get; set; }
public int SuccessorId { get; set; }
public VehicleSuccessorCategory(int order)
{
Order = order;
}
#endregion
#region Data
public int Order { get; set; }
#endregion
public bool IsTransient()
{
throw new NotImplementedException();
}
public IEnumerator GetEnumerator()
{
yield return Id;
yield return Order;
}
}
I tried
VehicleCategory vehicleCategory = _genericRepository.Get(aggregate.Id);
foreach (var successorCategory in aggregate.Successors)
{
var successorCategoryToRemove =
vehicleCategory.Successors.Where(e => e.SuccessorId == successorCategory.SuccessorId);
foreach (var vehicleSuccessorCategory in successorCategoryToRemove)
vehicleCategory.RemoveSuccessor(vehicleSuccessorCategory);
}
I'm having some problems trying to use the many-to-many relationship in EF Core 2.0. Here is me code:
Here are my entities:
User
public class User : IdentityUser
{
private User()
{
}
public String Name { get; private set; }
public ICollection<UserCourse> UserCourses { get; private set; }
public static User Create(string name, string username, string email)
{
var instance = new User
{
Id = Guid.NewGuid().ToString(),
};
instance.Update(name, username, email);
return instance;
}
public void Update(string name, string username, string email)
{
Name = name;
UserName = username;
Email = email;
}
public void Update(UserCreatingModel model)
{
this.UserName = model.Username;
this.Name = model.Name;
this.Email = model.Email;
}
public void Update(UserCourse userCourse)
{
if (UserCourses == null)
{
UserCourses = new List<UserCourse>() {userCourse};
}
else
{
UserCourses.Add(userCourse);
}
}
}
Course entity
public class Course
{
private Course() { }
public Guid Id { get; private set; }
public string Name { get; private set; }
public int Year { get; private set; }
public int Semester { get; private set; }
public List<Lesson> Lessons { get; private set; }
public ICollection<UserCourse> UserCourses { get; private set; }
public static Course Create(string name, int year, int semester, List<Lesson> lessons, List<User> professors)
{
var instance = new Course { Id = Guid.NewGuid() };
instance.Update(name, year, semester, lessons);
return instance;
}
public static Course Create(string name, int year, int semester)
{
var instance = new Course { Id = Guid.NewGuid() };
instance.Update(name, year, semester);
return instance;
}
public void Update(string name, int year, int semester, List<Lesson> lessons)
{
Name = name;
Year = year;
Semester = semester;
Lessons = lessons;
}
public void Update(string name, int year, int semester)
{
Name = name;
Year = year;
Semester = semester;
}
public void Update(UserCourse userCourse)
{
if (UserCourses == null)
{
UserCourses = new List<UserCourse>(){userCourse};
}
else
{
UserCourses.Add(userCourse);
}
}
public void Update(List<Lesson> lessons)
{
this.Lessons = lessons;
}
}
Join entity
public class UserCourse
{
private UserCourse() { }
public string UserId { get; private set; }
public User User { get; private set; }
public Guid CourseId { get; private set; }
public Course Course { get; private set; }
public static UserCourse CreateUserCourse(string userId, User user, Guid coursId, Course course)
{
var instance = new UserCourse
{
UserId = userId,
User = user,
CourseId = coursId,
Course = course
};
return instance;
}
}
This is my database context
public sealed class DatabaseContext : IdentityDbContext<User>, IDatabaseContext
{
public static readonly LoggerFactory MyLoggerFactory
= new LoggerFactory(new[] {new ConsoleLoggerProvider((_, __) => true, true)});
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
{
Database.EnsureCreated();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseLoggerFactory(MyLoggerFactory) // Warning: Do not create a new ILoggerFactory instance each time
.EnableSensitiveDataLogging();
public new DbSet<User> Users { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<Lesson> Lessons { get; set; }
public DbSet<UserCourse> UserCourses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Lesson>()
.HasOne(p => p.Course)
.WithMany(b => b.Lessons)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<UserCourse>()
.HasKey(uc => new {uc.UserId, uc.CourseId});
modelBuilder.Entity<UserCourse>()
.HasOne(uc => uc.User)
.WithMany(u => u.UserCourses)
.HasForeignKey(uc => uc.UserId);
modelBuilder.Entity<UserCourse>()
.HasOne(uc => uc.Course)
.WithMany(c => c.UserCourses)
.HasForeignKey(uc => uc.CourseId);
}
}
This is the repository where I try to add the 2 existing entities user and course to the joining table with the method AddCoursToProfessor
public class CoursesRepository : ACrudRepository<Course, Guid>, ICoursesRepository
{
public CoursesRepository(IDatabaseContext databaseContext) : base(databaseContext)
{
}
public override IReadOnlyList<Course> GetAll() => _databaseContext.Courses.Include(c => c.Lessons).Include(p => p.UserCourses).ToList();
public override Course GetById(Guid id)
=> _databaseContext.Courses.Include(c => c.Lessons).AsNoTracking().FirstOrDefault(c => c.Id.Equals(id));
public void AddCoursToProfessor(string profId, Guid coursId)
{
var professor = _databaseContext.Users.Include(u => u.UserCourses).FirstOrDefault(u => u.Id.Equals(profId));
var course = GetById(coursId);
var profCourse = UserCourse.CreateUserCourse(profId, professor, coursId, course);
professor.Update(profCourse);
_databaseContext.Users.Update(professor);
_databaseContext.SaveChanges();
}
}
The problem seems to be that when trying to add in the joining table, EF tries to add the course entity into it's table again and I get primary key violation error. I tried different approaches and none of them seem to work. I tried adding directly in UserCourse table but that would try to add both entities into their own tables, I tried deleting the entities before adding them to the join table, that didn't work either. I ran out of ideas, if someone has other ideas, or dealt with similar situations that would be much of help.
I forgot to mention that if I try to add a course by it self or an user, that would work, they both would be added to their tables, so I don't think that the problem is with the DB but with the configuration of the many-to-many relatonship
Ok... so I recently solved the problem. The problem was generated by the GetCourseById method because I was getting the entity AsNoTracking. The entity was not under the EF scope so it tried to create it.
As far as I know here's how I implement a MTM model.
In this instance, there are 3 parameters to take note.
CurrencyPairId is not unique. its a 2-unique parameter.
CurrencyId and IsMain is unique.
This is a trading exchange-styled composite key. i.e. EURUSD
EUR => Main Pair, USD => Counter Pair.
The class
/// <summary>
/// Partial currency pair.
/// </summary>
public class PartialCurrencyPair
{
public long CurrencyId { get; set; }
public long CurrencyPairId { get; set; }
public bool IsMain { get; set; } = false;
public CurrencyPair CurrencyPair { get; set; }
public Currency Currency { get; set; }
}
Currency
public class Currency : BaseEntityModel
{
[Key]
public long Id { get; set; }
public ICollection<PartialCurrencyPair> PartialCurrencyPairs { get; set; }
}
CurrencyPair
public class CurrencyPair : BaseEntityModel
{
[Key]
public long Id { get; set; }
// =========== RELATIONS ============ //
public ICollection<PartialCurrencyPair> PartialCurrencyPairs { get; set; }
}
Some of the mappings
in Currency;
entity.HasMany(c => c.PartialCurrencyPairs).WithOne(pcp => pcp.Currency).HasForeignKey(pcp => pcp.CurrencyId).OnDelete(DeleteBehavior.Restrict);
in CurrencyPair
entity.HasMany(cp => cp.PartialCurrencyPairs).WithOne(pcp => pcp.CurrencyPair).HasForeignKey(pcp => pcp.CurrencyPairId).OnDelete(DeleteBehavior.Restrict);
PartialCurrencyPair
builder.Entity<PartialCurrencyPair>(entity =>
{
entity.HasKey(pcp => new { pcp.CurrencyPairId, pcp.IsMain }).HasName("PartialCurrencyPair_CK_CurrencyPairId_IsMain");
});
Judging by your Course.cs, there's no Collection for UserCourse and why is it static?
Update, 2021 APR 6
EF Core has official documentation for the current practise for implementing MTM.
https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#many-to-many
I am new in Fluent NHibernet and i am using Fluent NHibernet in my asp.net application
this is my Poco class
public virtual int CategoryId { get; set; }
public virtual string CategoryName { get; set; }
public virtual bool IsActive { get; set; }
public virtual bool IsDeleted { get; set; }
My Mapping Class
public class clsCategoryMap : ClassMap<clsCategory>
{
public clsCategoryMap()
{
Id(x => x.CategoryId).Column("CategoryId").GeneratedBy.Assigned().Not.Nullable();
Map(x => x.CategoryName).Column("CategoryName").Not.Nullable();
Map(x => x.IsActive).Column("IsActive").Not.Nullable();
Map(x => x.IsDeleted).Column("IsDeleted").Not.Nullable();
Table("tblCategory");
}
}
Poco class and Mapping class both saprated in class Liberar like: DAL for Poco class and BLL For Mapping class.
And i create helper class it's below:
public class FNHelper
{
private static ISessionFactory _sessionfactory;
private static ISessionFactory SessionFactory
{
get
{
if (_sessionfactory == null) { InitializationSessionFactory(); }
return _sessionfactory;
}
}
private static void InitializationSessionFactory()
{
_sessionfactory = Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008
.ConnectionString(#"Server=test\SQLEXPRESS;Database=TestDB;User ID=sa;Password=root;")
.DefaultSchema("dbo")
.ShowSql()
)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<clsCategory>())
.ExposeConfiguration((cfg => new SchemaUpdate(cfg).Execute(true, true)))
.BuildSessionFactory();
}
public static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
private static void BuildSchema(NHibernate.Cfg.Configuration configuration)
{
String SqliteRefFileName = #"D:\Projects\MeshpsDB.sql";
if (File.Exists(SqliteRefFileName))
File.Delete(SqliteRefFileName);
new SchemaExport(configuration)
.Create(true, true);
}
}
And finally i am doing in my form it's below:
protected void btnSave_Click(object sender, EventArgs e)
{
using (var session = FNHelper.OpenSession())
{
using (var tranction = session.Transaction)
{
var objCategory = new clsCategory
{
CategoryId = 0,
CategoryName = txtName.Text.Trim(),
IsActive = true,
IsDeleted = false
};
session.Save(objCategory);
tranction.Commit();
}
}
}
when i click on button then i am getting
so every one please tell me how can i solve this issue.
change m.FluentMappings.AddFromAssemblyOf<clsCategory>() to m.FluentMappings.AddFromAssemblyOf<clsCategoryMap>() because its the mappingss you want to add and these reside in another assembly.
I am using Table Per Hierarchy (TPH) architecture to work with an existing database schema. When I attempt to use a custom Discriminator field name a DbEntityValidationException:
Property: CourseType Error: The CourseType field is required.
Models
public abstract class Course {
public int ID { get; set; }
public string Name { get; set; }
public string CourseType { get; set; }
}
public class OnlineCourse : Course {
public string Url { get; set; }
}
public class OnsiteCourse : Course {
public string Location { get; set; }
}
Entity Type Configurations
public class CourseMap : EntityTypeConfiguration<Course> {
public CourseMap() {
this.HasKey(x => x.ID);
this.Property(x => x.Name).HasMaxLength(100).IsRequired();
this.Property(x => x.CourseType).HasMaxLength(128).IsRequired();
}
}
public class OnlineCourseMap : EntityTypeConfiguration<OnlineCourse> {
public OnlineCourseMap() {
this.Property(x => x.Url).HasMaxLength(500);
}
}
public class OnsiteCourseMap : EntityTypeConfiguration<OnsiteCourse> {
public OnsiteCourseMap() {
this.Property(x => x.Location).HasMaxLength(100);
}
}
Data Context
public class EntityContext : DbContext {
public EntityContext(): base("Name=EntityContext") {
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Configurations.Add(new CourseMap());
modelBuilder.Configurations.Add(new OnlineCourseMap());
modelBuilder.Configurations.Add(new OnsiteCourseMap());
modelBuilder.Entity<Course>()
.Map<OnlineCourse>(x => x.Requires("CourseType").HasValue("Online"))
.Map<OnsiteCourse>(x => x.Requires("CourseType").HasValue("Onsite"));
}
public DbSet<Course> Courses { get; set; }
}
Code Executed
using (var ctx = new EntityContext()) {
ctx.Courses.Add(new OnlineCourse() {
Name = "Online 1",
Url = "Http://www.online.com"
});
ctx.SaveChanges();
}
I would have expected an exception like "CourseType cannot be used as discriminator and also be a mapped coulmun" (I evidently don't remember the correct error message). But CourseType can't be part of the model as a mapped column when it is a discriminator. The usual pattern is to see it in the mapping configuration only (as you have). Nowhere else.
So it may help if you remove CourseType from Course. I hope you don't need it for any other logic.
Here is my model classes:
public abstract class BaseEntity
{
[Key]
public Guid Guid { get; set; }
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
public int Id
{
get;
set;
}
}
public class Menu : Abstract.BaseEntity
{
public string Title { get; set; }
public string Url { get; set; }
}
public class SpaceShipEntities : DbContext
{
public DbSet<Menu> Menu { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
I use migrations. And there is my configuration class:
internal sealed class Configuration : DbMigrationsConfiguration<SpaceProject.Models.SpaceShipEntities>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(SpaceProject.Models.SpaceShipEntities context)
{
context.Menu.AddOrUpdate(
c => c.Title,
new Menu() { Guid = Guid.NewGuid(), Title = "Главная" }
);
}
}
When i use package command "Update-Database -Verbose" in empty database new rows has inserted to database. But when i use "Update-Database -Verbose" second time, and Seed method tries to update rows, i have following error:
When updating records the error occurred. For details, see the inner exception. ---> System.InvalidOperationException: Changing column template "Identity" is not supported. Column: "Id". Table: "CodeFirstDatabaseSchema.Menu".
The Guid will be indexed anyway if you use it as a key. I would use Id as the key and decorate Guid with [DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)] then seed it with:
protected override void Seed(SpaceProject.Models.SpaceShipEntities context)
{
context.Menu.AddOrUpdate(
c => c.Guid,
new Menu() { Guid = Guid.Parse("..."), Title = "Главная" }
);
}