EFCore Generic Repository and UnitOfWork Design Pattern - entity-framework-core

when im trying to create new data and save it, im getting error at the
public int Complete()
{
return _context.SaveChanges();
}
and error is saying me that:
The value of 'Agency.ID' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known. .
i have a Base class like that:
public class Base
{
protected Base()
{
CreatedDate = DateTime.Now;
IsDeleted = false;
ModifiedDate = null;
}
public int ID { get; set; }
public int? CreatedUserId { get; set; }
public int? ModifiedUserId { get; set; }
public string CreatedUserType { get; set; }
public string ModifiedUserType { get; set; }
public DateTime? CreatedDate { get; set; }
public DateTime? ModifiedDate { get; set; }
public bool? IsActive { get; set; }
public bool? IsDeleted { get; set; }
}
i have a Agency class like that :
public class Agency : Base
{
public Agency()
{
AgencyIsComplated = false;
}
[Required, StringLength(255)]
public string AgencyName { get; set; }
[Required, StringLength(255)]
public string AgencyPhoto { get; set; }
[Required, StringLength(255)]
public string AgencyEMail { get; set; }
[Required, StringLength(13)]
public string AgencyPhone { get; set; }
[StringLength(13)]
public string AgencyBPhone { get; set; }
[StringLength(255)]
public string AgencyInfo { get; set; }
[Required, StringLength(255)]
public string AgencyTitle { get; set; }
[Required, StringLength(255)]
public string AgencyLink { get; set; }
public int AgencyExportArea { get; set; } // Join table ile yapılacak,ayrı bir tabloda tutulacak
[Required, StringLength(255)]
public string AgencyInstagram { get; set; }
public string AgencyTwitter { get; set; }
public string AgencyFacebook { get; set; }
public string AgencyLinkedin { get; set; }
public string AgencyYoutube { get; set; }
public bool AgencyIsComplated { get; set; }
[ForeignKey("CompanyID")]
public Company Company { get; set; }
[ForeignKey("LogID")]
public Log Log { get; set; }
public virtual ICollection<AgencyCompany> AgencyCompanies { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<Log> Logs { get; set; }
}
public class AgencyConfiguration : IEntityTypeConfiguration<Agency>
{
public void Configure(EntityTypeBuilder<Agency> builder)
{
builder.HasKey(agency => agency.ID);
builder.HasMany(a => a.Logs)
.WithOne(a => a.Agency)
.HasForeignKey(a=>a.ID)
.OnDelete(DeleteBehavior.Restrict);
builder.HasMany(us => us.Users)
.WithOne(us => us.Agency)
.HasForeignKey(au=>au.ID)
.OnDelete(DeleteBehavior.Restrict);
builder.HasMany(ac => ac.AgencyCompanies)
.WithOne(ac => ac.Agency)
.OnDelete(DeleteBehavior.Restrict);
}
}
and i have got a UnitOfWork like that:
public class UnitOfWork : IUnitOfWork
{
private TradeTurkDBContext _context;
public UnitOfWork(TradeTurkDBContext context)
{
_context = context;
RepositoryAgency = new RepositoryAgency(_context);
}
public IRepository Repository { get; private set; }
public IRepositoryAgency RepositoryAgency { get; private set; }
public int Complete()
{
return _context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}
im inheriting that ID on my Base Model.
the problem is getting solved when im not defining ID in the base model but i allready set up my mapping on it.
so how can i solve that error without using AgencyID in the Agency model ?

The foreign key is in the details (or child) table. Therefore, e.g. a user, should have an AgencyId as foreign key.
builder.Entity<User>()
.HasOne(u => u.Agency)
.WithMany(a => a.Users)
.HasForeignKey(u => u.AgencyId)
.OnDelete(DeleteBehavior.Restrict);
This key automatically points to the primary key of the master (or parent) table.
User.ID is a primary key. User.AgencyId is a foreign key which (automatically) relates to the primary key Agency.ID.
E.g. see: Configure One-to-Many Relationships using Fluent API in Entity Framework Core

Related

Defining the one to many relationship in OnModelCreating using Entity Framework Core 3.1

I am new to Entity Framework Core 3.1 and trying to define the one-to-many relationship between two tables. I am currently struggling and getting compilation errors. Could somebody tell me what the problem could be.
The error is:
PersonNote does not contain the definition for PersonNote
I am currently getting is at line
entity.HasOne(d => d.PersonNote)
How else could I define one-to-many relationship?
The two tables are Person and PersonNote. One Person can have many PersonNotes. I have defined the models for them
public class Person
{
public int Id { get; set; }
public int? TitleId { get; set; }
public string FirstName { get; set; }
public string FirstNamePref { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public DateTime? DateOfBirth { get; set; }
public string Gender { get; set; }
public int AddressId { get; set; }
public string TelephoneNumber { get; set; }
public string MobileNumber { get; set; }
public string Email { get; set; }
public int? PartnerId { get; set; }
public bool Enabled { get; set; }
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
public string ModifiedBy { get; set; }
public DateTime Modified { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordStartDateTime { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordEndDateTime { get; set; }
public Address Address { get; set; }
public Title Title { get; set; }
public Client Client { get; set; }
internal static IEnumerable<object> Include(Func<object, object> p)
{
throw new NotImplementedException();
}
public PersonNote PersonNote { get; set; }
}
public class PersonNote
{
public int Id { get; set; }
public int PersonId { get; set; }
public string Note { get; set; }
public int AuthorId { get; set; }
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordStartDateTime { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordEndDateTime { get; set; }
}
public IEnumerable<PersonNote> GetPersonNotes(int personId)
{
var PersonNotes = PersonNote
.Include(x => x.)
.Where(x => x.Id == personId)
.ToList();
return PersonNotes;
}
I have tried the following in OnModelCreating:
modelBuilder.Entity<PersonNote>(entity =>
{
entity.ToTable("PersonNote", "common");
entity.HasOne(d => d.PersonNote)
.WithMany(p => p.Person)
.HasForeignKey(d => d.PersonId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_commonPersonNote_commonPerson");
});
You should have have something like this (other properties are omitted):
class Person
{
[Key]
public int Id { get; set; }
public List<PersonNote> PersonNotes { get; set; }
}
class PersonNote
{
[Key]
public int Id { get; set; }
public int PersonId { get; set; }
}
class StackOverflow : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<PersonNote> PersonNotes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasMany(p => p.PersonNotes)
.WithOne()
.HasForeignKey(p => p.PersonId);
}
}

Getting ObjectContext error even after calling ToList

When calling the method directly below I get a ObjectDisposedException when calling Mapper.Map with the retrieved list.
System.ObjectDisposedException: The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
public IEnumerable<Models.Provider> Get(string owner)
{
List<Data.Models.Provider> providers;
using (var db = new Data.ProviderDirectoryContext())
{
providers = db.Providers.Where(p => p.Owner.Name == owner).ToList();
}
var dtoProviders = Mapper.Map<List<Data.Models.Provider>, List<Models.Provider>>(providers);
return dtoProviders;
}
I previously had the code like this (below), I wasn't getting an error, but the database was getting pounded when doing the mapping, and it was taking too long. I don't want to hit the database, when doing the mapping.
public IEnumerable<Models.Provider> Get(string owner)
{
using (var db = new Data.ProviderDirectoryContext())
{
var providers = db.Providers.Where(p => p.Owner.Name == owner).ToList();
var dtoProviders = Mapper.Map<List<Data.Models.Provider>, List<Models.Provider>>(providers);
return dtoProviders;
}
}
How can I retrieve all the data before doing the mapping?
Here is the DbContext and the Data.Models.Provider for your reference.
public class ProviderDirectoryContext : DbContext
{
public DbSet<Owner> Owners { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<LocationAuditLog> LocationAuditLog { get; set; }
public DbSet<Office> Offices { get; set; }
public DbSet<OfficePhoneNumber> OfficePhoneNumbers { get; set; }
public DbSet<OfficeAuditLog> OfficeAuditLog { get; set; }
public DbSet<OfficeDay> OfficeDays { get; set; }
public DbSet<Provider> Providers { get; set; }
public DbSet<ProviderPhoneNumber> ProviderPhoneNumbers { get; set; }
public DbSet<ProviderAuditLog> ProviderAuditLog { get; set; }
public DbSet<ProviderType> ProviderTypes { get; set; }
public DbSet<ProviderSpecialty> ProviderSpecialties { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Provider>().HasRequired(cn => cn.Owner).WithMany().WillCascadeOnDelete(false);
modelBuilder.Entity<Office>().HasRequired(cn => cn.Owner).WithMany().WillCascadeOnDelete(false);
}
}
public class Provider
{
public int Id { get; set; }
public int OwnerId { get; set; }
public virtual Owner Owner { get; set; }
public int? ProviderTypeId { get; set; }
public virtual ProviderType ProviderType { get; set; }
public int? ProviderSpecialtyId { get; set; }
public virtual ProviderSpecialty ProviderSpecialty { get; set; }
[Required]
[StringLength(75)]
public string FirstName { get; set; }
[StringLength(75)]
public string MiddleName { get; set; }
[Required]
[StringLength(75)]
public string LastName { get; set; }
[StringLength(100)]
public string EmailAddress { get; set; }
public virtual ICollection<ProviderPhoneNumber> PhoneNumbers { get; set; }
public string Note { get; set; }
public DateTime? InactiveOn { get; set; }
public int OfficeId { get; set; }
public virtual Office Office { get; set; }
public virtual ICollection<ProviderAuditLog> AuditLog { get; set; }
[Required]
public DateTime CreatedOn { get; set; }
[Required]
[StringLength(75)]
public string CreatedBy { get; set; }
[Required]
public DateTime ModifiedOn { get; set; }
[Required]
[StringLength(75)]
public string ModifiedBy { get; set; }
}
Thanks for the help!
The problem is that the Models.Provider class contains other classes like Models.Office, and Models.PhoneNumbers that were not eagerly loaded by the query. In addition to that, the Models.Provider class needs to be flattened. The Mapper wants to recursively map everything, and it keeps going down to the next class. For example, Provider.Office.Location.Offices.
The solution is to flatten Models.Provider and add .Include() to the query so it eagerly loads the data required.
I'll clean this up a bit more, but this is currently working.
public IEnumerable<Models.Provider> Get(string owner)
{
List<Data.Models.Provider> providers;
using (var db = new Data.ProviderDirectoryContext())
{
providers = db.Providers
.Where(p => p.Owner.Name == owner)
.Include("ProviderType")
.Include("ProviderSpecialty")
.Include("Office")
.Include("PhoneNumbers")
.ToList();
}
var dtoProviders = Mapper.Map<List<Data.Models.Provider>, List<Models.Provider>>(providers);
return dtoProviders;
}
public class Provider
{
public int Id { get; set; }
public int OwnerId { get; set; }
public int OfficeId { get; set; }
public string OfficeName { get; set; }
public int? ProviderTypeId { get; set; }
public string ProviderTypeName { get; set; }
public int? ProviderSpecialtyId { get; set; }
public string ProviderSpecialtyName { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
public string Note { get; set; }
public DateTime? InactiveOn { get; set; }
public DateTime CreatedOn { get; set; }
public string CreatedBy { get; set; }
public DateTime ModifiedOn { get; set; }
public string ModifiedBy { get; set; }
}
I am not sure how much this will help with performance but declaring the variable you don't want to dispose outside the using statement should fix your dispose exception.
public IEnumerable<Models.Provider> Get(string owner)
{
IEnumerable<Models.Provider> dtoProviders;
using (var db = new Data.ProviderDirectoryContext())
{
List<Data.Models.Provider> providers = db.Providers.Where(p => p.Owner.Name == owner).ToList();
dtoProviders = Mapper.Map<List<Data.Models.Provider>, List<Models.Provider>>(providers);
}
return dtoProviders;
}

One to many relation with cascade giving error

I am learning EF Code First with migrations, I have 3 entities :
[User] 1--->* [Call] 1--->* [ETA]
Code :
User.cs
public class User
{
[Key]
public int Id { get; set; }
public Guid LongId { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Call> Calls { get; set; } // many calls
public User()
{
LongId = Guid.NewGuid();
}
}
Call.cs
public class Call
{
[Key]
public int Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string BreakdownNo { get; private set; }
[Required,MaxLength(32)]
public string Customer { get; set; }
[Required,MaxLength(32)]
public string TrailerNo { get; set; }
[Required, MaxLength(32)]
public string DepotContact { get; set; }
[Required, MaxLength(48), RegularExpression(#"^[_a-z0-9-]+(\.[_a-z0-9-]+)*#[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$")]
public string DepotEmail { get; set; }
[Required, MinLength(9), MaxLength(32)]
public string DepotPhone { get; set; }
[Required, MaxLength(32)]
public string DriverContact { get; set; }
[Required, MinLength(9), MaxLength(32), RegularExpression(#"^(7\d{3}|\(?07\d{3}\)?)\s?\d{3}\s?\d{3}$")]
public string DriverPhone { get; set; }
[Required, MaxLength(256)]
public string LocatedAtFreeText { get; set; }
[Required, MaxLength(8), RegularExpression(#"^([A-PR-UWYZ0-9][A-HK-Y0-9][AEHMNPRTVXY0-9]?[ABEHMNPRVWXY0-9]? {0,1}[0-9][ABD-HJLN-UW-Z]{2}|GIR 0AA)$")]
public string LocatedAtPostCode { get; set; }
[Required, MaxLength(16)]
public string StartupNo { get; set; }
[Required]
public bool IsLoaded { get; set; }
[Required, MaxLength(256)]
public string FaultDescription { get; set; }
[Required]
public DateTime StartTime { get; set; }
public DateTime? EndTime { get; set; }
public string Status { get; set; }
public virtual User Controller { get; set; } // 1 controller
public virtual ICollection<ETA> ETAs { get; set; } // many ETAs
public Call()
{
StartTime = DateTime.Now;
ETAs = new List<ETA> { new ETA() };
Status = "Logged";
}
}
ETA.c
public class ETA
{
[Key]
public int Id { get; set; }
[Required]
public TimeSpan Value { get; set; }
public int CallId { get; set; }
public ETA()
{
Value = TimeSpan.FromMinutes(90);
}
}
I would like it so when I delete the User it deletes all of the Calls for the User, which in turn deletes all of the ETAs for those Calls.
When I delete a User row from the Database (using database explorer) it gives me an error :
No rows were deleted.
A problem occurred attempting to delete row 201.
Error Source: .Net SqlClient Data Provider.
Error Message: The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.Calls_dbo.Users_Controller_Id". The conflict occurred in database "BreakdownDb", table "dbo.Calls", column 'Controller_Id'.
You can turn on the Cascade Delete option in Entity Framework, here you will find more info:
http://blogs.msdn.com/b/alexj/archive/2009/08/19/tip-33-how-cascade-delete-really-works-in-ef.aspx
The solution was to add OnModelCreating method to my DbContext class :
public class BreakdownDb : DbContext
{
public DbSet<Call> Calls { get; set; }
public DbSet<User> Users { get; set; }
public BreakdownDb(): base("name=DefaultConnection") {}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasMany(x => x.Calls).WithRequired();
modelBuilder.Entity<Call>().HasMany(x => x.ETAs).WithRequired();
}
}

How to properly map entities using Fluent API?

I have two entities, a User and a UserProfile. The PK of User is UserId, the PK of UserProfile is UserProfileId. Every time a new user is created in my app, I create a new UserProfile whose PK is the same as the PK in User. When I then try to go update properties on the UserProfile I end up getting multiplicity errors or schema invalid errors. Here are my two entities:
public class User
{
public Guid UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? PhoneExtension { get; set; }
public string Comment { get; set; }
public Boolean IsApproved { get; set; }
public int PasswordFailuresSinceLastSuccess { get; set; }
public DateTime? LastPasswordFailureDate { get; set; }
public DateTime? LastActivityDate { get; set; }
public DateTime? LastLockoutDate { get; set; }
public DateTime? LastLoginDate { get; set; }
public string ConfirmationToken { get; set; }
public DateTime? CreateDate { get; set; }
public Boolean IsLockedOut { get; set; }
public DateTime? LastPasswordChangedDate { get; set; }
public string PasswordVerificationToken { get; set; }
public DateTime? PasswordVerificationTokenExpirationDate { get; set; }
public virtual ICollection<Role> Roles { get; set; }
public virtual UserProfile UserProfile { get; set; }
}
public class UserProfile
{
public Guid UserProfileId { get; set; }
public virtual User ProfileOwner { get; set; }
public Int64? HomePhone { get; set; }
public Int64? MobilePhone { get; set; }
public virtual User Manager { get; set; }
}
..and here are my only defined relationships using Fluent API.
modelBuilder.Entity<UserProfile>()
.HasKey(e => e.UserProfileId);
modelBuilder.Entity<UserProfile>()
.Property(e => e.UserProfileId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<UserProfile>()
.HasRequired(e => e.ProfileOwner)
.WithRequiredDependent(r => r.UserProfile);
Finally, my UserService creates a new user and at the same time creates a new UserProfile whose Guid UserProfileId is the same as the User's Guid UserId. Right after the user and profile are created, I try to update the manager in the UserProfile with my UserProfileService using this:
public void UpdateUserProfile(UserProfile updatedUserProfile)
{
UserProfile oldUserProfile = GetUserProfileByID(updatedUserProfile.UserProfileId);
oldUserProfile.Manager = updatedUserProfile.Manager;
oldUserProfile.HomePhone = updatedUserProfile.HomePhone;
oldUserProfile.MobilePhone = updatedUserProfile.MobilePhone;
this.SetEntityState(oldUserProfile, EntityState.Modified);
this.UnitOfWork.SaveChanges();
}
The this.SetEntityState line throws this error:
Multiplicity constraint violated. The role 'UserProfile_ProfileOwner_Source' of the relationship 'WhelenPortal.Data.Context.UserProfile_ProfileOwner' has multiplicity 1 or 0..1.
I've been trying to get this working for TWO DAYS now, PLEASE HELP!!! Thanks in advance.
As requested, here is some additional information. I'm using the repository pattern and unit of work here. My GetUserProfileById code is below. The service uses the repository so I show both.
public UserProfile GetUserProfileByID(Guid id)
{
if (id == null)
throw new BusinessServicesException(Resources.UnableToRetrieveUserProfileExceptionMessage, new ArgumentNullException("id"));
try
{
Model.UserProfile userProfile = _userProfileRepository.GetUserProfileByID(id);
if (userProfile != null)
return ToServicesUserProfile(userProfile);
return null;
}
catch (InvalidOperationException ex)
{
throw new BusinessServicesException(Resources.UnableToRetrieveUserProfileExceptionMessage, ex);
}
}
..and the repository:
public UserProfile GetUserProfileByID(Guid id)
{
return this.GetDbSet<UserProfile>().Find(id);
}
So after much playing around this is what ended up working for me, hopefully it can help someone else in some fashion. My User class stayed exactly the same but my UserProfile class changed to this:
public class UserProfile
{
public Guid UserProfileId { get; set; }
public virtual User ProfileOwner { get; set; }
public Guid? ManagerId { get; set; }
public virtual User Manager { get; set; }
public Int64? HomePhone { get; set; }
public Int64? MobilePhone { get; set; }
}
And here is the fluent mapping:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>()
.HasOptional(u => u.UserProfile)
.WithRequired(u => u.ProfileOwner);
modelBuilder.Entity<UserProfile>()
.HasOptional(u => u.Manager)
.WithMany()
.HasForeignKey(u => u.ManagerId);
}

Entity Framework Code First and Invalid Object Name Error

I have a composite table called ImporterState, that are tied to a table called Importer and State. The error happens here context.Importers.Include(q => q.States). Why is this happening?
{"Invalid object name 'ImporterStates'."}
[Table("HeadlineWebsiteImport", Schema = "GrassrootsHoops")]
public class Importer
{
public int Id { get; set; }
public string Name { get; set; }
public string RssUrl { get; set; }
public string Type { get; set; }
public string Keywords { get; set; }
public bool Active { get; set; }
public DateTime DateModified { get; set; }
public DateTime DateCreated { get; set; }
public int WebsiteId { get; set; }
public HeadlineWebsite Website { get; set; }
[InverseProperty("Importers")]
public ICollection<State> States { get; set; }
}
[Table("State", Schema = "GrassrootsHoops")]
public class State
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Abbr { get; set; }
[InverseProperty("States")]
public ICollection<Headline> Headlines { get; set; }
[InverseProperty("States")]
public ICollection<Importer> Importers { get; set; }
}
The many to many is not possible using attributes only.
try using something like:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Importer>()
.HasMany(i => i.States)
.WithMany(s => s.Importers)
.Map(m =>
{
m.MapLeftKey("ImporterId");
m.MapRightKey("StateId");
m.ToTable("ImporterState");
});
}