I am creating a custom table and using Identity. I have a context for adding rows to this table aswell.
code:
public class UserRoleAccess
{
[Key]
public string Id { get; set; }
public string UserRoleId { get; set; }
[ForeignKey("Id")]
public virtual ApplicationRole ApplicationRole { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
public int UserRoleAccessType { get; set; }
}
db context:
public class UserRoleAccessContext : DbContext
{
public UserRoleAccessContext() : base("DMConnection")
{
Database.SetInitializer<PersonContext>(null);
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
public static UserRoleAccessContext Create()
{
return new UserRoleAccessContext();
}
public DbSet<UserRoleAccess> UserRoleAccess { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// IMPORTANT: we are mapping the entity User to the same table as the entity ApplicationUser
//modelBuilder.Entity<IdentityUserLogin>().ToTable("Users").HasKey(e => e.UserId);
}
public DbQuery<T> Query<T>() where T : class
{
return Set<T>();
}
}
bindings:
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<ApplicationUser>().HasRequired(p => p.Person)
.WithMany(b => b.Users);
modelBuilder.Entity<ApplicationUser>().HasRequired(p => p.Person)
.WithMany(b => b.Users)
.HasForeignKey(p => p.PersonId);
modelBuilder.Entity<UserRoleAccess>().HasRequired(p => p.ApplicationRole)
.WithMany(b => b.UserRoleAccess);
modelBuilder.Entity<UserRoleAccess>().HasRequired(p => p.ApplicationRole)
.WithMany(b => b.UserRoleAccess)
.HasForeignKey(p => p.UserRoleId);
}
Executing this code when I get an error:
public static void CreateUserRoleAccess(string name, int type, string id)
{
var userAccessContext = new UserRoleAccessContext();
var userRoleAccess = new UserRoleAccess();
userRoleAccess.UserRoleId = id;
userRoleAccess.IsActive = true;
userRoleAccess.UserRoleAccessType = type;
userRoleAccess.Name = name;
userAccessContext.UserRoleAccess.Add(userRoleAccess);
}
error:
One or more validation errors were detected during model generation:
Project.Models.IdentityUserRole: : EntityType 'IdentityUserRole' has
no key defined. Define the key for this EntityType. IdentityUserRoles:
EntityType: EntitySet 'IdentityUserRoles' is based on type
'IdentityUserRole' that has no keys defined.
I assume I am missing a binding somewhere but I can't figure out where?
EDIT:
I changed UserRoleAccess class. I changed the Id from string to long and
I tried this in TSQL
INSERT INTO UserRoleAccess (UserRoleId,IsActive, Name, UserRoleAccessType) VALUES('e5b2e76a-a106-4076-b219-6987411995e7', 1, 'TEST', 1)
It worked. Although Im still getting the same error when running it from my application
EDIT 2
added this in my bindings :
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
Still the same result
So funny thing is.
My second edit was actually correct. but for some reason it did not work when I migrated and updated db with code first.
I deleted all the tables and created them again via migrations and it work.
Although with the edit I had to put this code in both db contexts:
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
Related
I have a table called contracts which has the id of entity 1 and entity 2. I use this to create links between entities, however I can't seem to get the definition of the dbContext correct. I can get the contracts to load but only one entity. When I view the contract either Entity1 is loaded or Entity2 is loaded but never both.
The models look like this:
public class Contract
{
public int Entity1ID { get; set; }
public int Entity2ID { get; set; }
public Entity Entity1 { get; set; }
public Entity Entity2 { get; set; }
}
My entity class looks like this
public class Entity
{
public int ID { get; set; }
public ICollection<Contract> Contracts1 { get; set; };
public ICollection<Contract> Contracts2 { get; set; };
}
This is my dbContext
public class EntityDbContext : DbContext
{
public EntityDbContext(DbContextOptions<EntityDbContext> options)
: base(options)
{
}
public DbSet<Models.Entity> Entities { get; set; }
public DbSet<Models.Contract> Contracts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.HasKey(n => n.ID);
modelBuilder.Entity<Contract>()
.HasKey(n => new { n.Entity1ID, n.Entity2ID });
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity1)
.WithMany(n => n.Contracts1)
.HasForeignKey(n => n.Entity1ID);
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity2)
.WithMany(n => n.Contracts2)
.HasForeignKey(n => n.Entity2ID);
}
}
From your model design , there is a self-referencing many-to-many relationship in Entity model , so you should change DeleteBehavior to Restrict(the default is cascade) like below:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.HasKey(n => n.ID);
modelBuilder.Entity<Contract>()
.HasKey(n => new { n.Entity1ID, n.Entity2ID });
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity1)
.WithMany(n => n.Contracts1)
.HasForeignKey(n => n.Entity1ID)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity2)
.WithMany(n => n.Contracts2)
.HasForeignKey(n => n.Entity2ID)
.OnDelete(DeleteBehavior.Restrict);
}
For loading related data , you could use the Include method that Sharif suggested above to specify related data to be included in query results
var result = _context.Contracts
.Include(c => c.Entity1)
.Include(c => c.Entity2)
.ToList();
Result
I have tables: Vendor and Brand and base abstract class
public abstract class Entity<TEntity, TKeyType> : IEntity<TEntity, TKeyType>
where TEntity : class
{
[Key]
public virtual TKeyType ID { get; set; }
public virtual bool IsDeleted { get; set; }
#region Equals
public virtual bool Equals(Entity<TEntity, TKeyType> other)
{
if (ReferenceEquals(this, other))
return true;
if (other == null || !(other is TEntity))
return false;
return ID.Equals(other.ID);
}
public override bool Equals(object obj)
{
var compareTo = obj as Entity<TEntity, TKeyType>;
return Equals(compareTo);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
#endregion
}
Entity:
public partial class Vendor : Entity<Vendor, long>
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Vendor()
{
Brand = new HashSet<Brand>();
}
[Required]
[StringLength(32)]
public string Name { get; set; }
public Guid Guid { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Brand> Brand { get; set; }
}
and
public partial class Brand : Entity<Brand, long>
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Brand()
{
SubBrand = new HashSet<SubBrand>();
}
[Required]
[StringLength(64)]
public string Name { get; set; }
public Guid Guid { get; set; }
public long VendorID { get; set; }
public virtual Vendor Vendor { get; set; }
}
ODMSDBContext:
public partial class ODMSDBContext : DbContext
{
public ODMSDBContext() : base("name=ODMSConnection") { }
public virtual DbSet<Vendor> Vendor { get; set; }
public virtual DbSet<Brand> Brand { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Brand>()
.Property(e => e.ExtraCode)
.IsUnicode(false);
modelBuilder.Entity<Brand>()
.Property(e => e.Name)
.IsUnicode(false);
modelBuilder.Entity<Brand>()
.HasMany(e => e.SubBrand)
.WithRequired(e => e.Brand)
.HasForeignKey(e => e.BrandID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Vendor>()
.Property(e => e.Name)
.IsUnicode(false);
modelBuilder.Entity<Vendor>()
.HasMany(e => e.Brand)
.WithRequired(e => e.Vendor)
.HasForeignKey(e => e.VendorID)
.WillCascadeOnDelete(false);
}
}
in Service exist method - Create
public override void Create(IEnumerable<EntityModel> models)
{
var entities = new List<Brand>();
foreach (var model in models)
{
var entityModel = model as BrandModel;
var entity = new Brand
{
Guid = entityModel.Guid,
VendorID = entityModel.VendorID,
Name = entityModel.Name,
SortOrder = entityModel.SortOrder,
ExtraCode = entityModel.ExtraCode
};
entities.Add(entity);
}
_repository.Create(entities);
_repository.Save();
}
and sample of code from Repository (_repository)
public class EntityRepository<TEntity, TKeyType> : IEntityRepository<ODMSDBContext, TEntity, TKeyType>
where TEntity : class, IEntity<TEntity, TKeyType>
{
private readonly ODMSDBContext _context;
private DbSet<TEntity> DbSet => _context.Set<TEntity>();
public EntityRepository(ODMSDBContext context)
{
_context = context;
}
...
public void Create(IEnumerable<TEntity> entities)
{
DbSet.AddRange(entities);
}
public void Save()
{
_context.SaveChanges();
transaction.Commit();
}
}
}
on saving I get an error:
Exception thrown: 'System.InvalidOperationException' in EntityFramework.dll
Additional information: The operation failed: The relationship could not be > changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
before saving I checked Brand. Vendor in most of all is null, but VendorID is filled.
I tried to add this in Service in method Create
Vendor = _vendorRepository.GetById(entityModel.VendorID)
but after AddRange data from Vendor field are disappeared %)
upd. I have created small video that describe strange behaviour - http://screencast.com/t/RI32v4gu
I don't see a Key data annotation or you setting the actual keys up in Fluent API.
Try changing your modelBuilder overrides to explicitly set the foreign keys on each entity.
public partial class ODMSDBContext : DbContext
{
public ODMSDBContext() : base("name=ODMSConnection") { }
public virtual DbSet<Vendor> Vendor { get; set; }
public virtual DbSet<Brand> Brand { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Brand>()
.Property(e => e.ExtraCode)
.IsUnicode(false);
modelBuilder.Entity<Brand>()
.Property(e => e.Name)
.IsUnicode(false);
modelBuilder.Entity<Brand>()
.HasMany(e => e.SubBrand)
.WithRequired(e => e.Brand)
.HasForeignKey(e=>e.ID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Vendor>()
.Property(e => e.Name)
.IsUnicode(false);
modelBuilder.Entity<Vendor>()
.HasMany(e => e.Brand)
.WithRequired(e => e.Vendor)
.HasForeignKey(e=>e.ID)
.WillCascadeOnDelete(false);
}
}
I need to say sorry for wasting your time. I hasn't copied all my code for abstract class Entity and that's why you couldn't help me. In abstract class Entity, I have realized base methods for comparing.
When I tried to add new Brands to Vendor, field ID in all Brands was equal to 0. This value was compare to exist in HashSet, with the help of my Base Methods for Comparing, and new records were not added because Brand with ID = 0 does exist in this list.
I have commented Base Methods for Comparing and everything is working :)
ps. splecial thanks to #grek40 ;)
I have the following Entities which I am persisting using EntityFramework CodeFirst:
public class User {
RedGroup RedGroup { get; protected set; }
virtual ICollection<GreenGroup> GreenGroups { get; }
int Id { get; protected set; }
int? RedGroupId { get; protected set; }
}
public abstract class Group {
int Id { get; protected set; }
virtual ICollection<User> Users { get; protected set; }
}
public class RedGroup : Group {
// Other properties
}
public class GreenGroup : Group {
// Other properties
}
Essentially, the user can belong to zero or one red groups, and more than one green group. Each group has a collection of users that belong to it.
I am trying to set up EF using CodeFirst with TPT and am having trouble sorting the mappings. At the moment, I have the following in OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new RedGroupMap());
modelBuilder.Configurations.Add(new GreenGroupMap());
modelBuilder.Configurations.Add(new UserMap());
}
These are the mapping classes:
public abstract class GroupMap<T> : EntityTypeConfiguration<T>
where T : Group {
public GroupMap() {
this.ToTable("Groups");
this.HasKey(t => t.Id);
this.Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("Id");
// Also has other non-relationship mappings
}
}
public class RedGroupMap() : GroupMap<RedGroup> {
public RedGroupMap() {
this.ToTable("RedGroups");
// Also has other non-relationship mappings
}
}
public class GreenGroupMap() : GroupMap<GreenGroup> {
public GreenGroupMap() {
this.ToTable("GreenGroups");
this.HasMany(c => c.Users)
.WithMany(p => p.GreenGroups)
.Map(m =>
{
m.MapLeftKey("GreenGroupId");
m.MapRightKey("UserId");
m.ToTable("Users_GreenGroups");
});
// Also has other non-relationship mappings
}
}
public class UserMap() : EntityTypeConfiguration<User> {
this.ToTable("Users");
this.HasKey(t => t.Id);
this.Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("Id");
this.HasOptional(t => t.RedGroup)
.WithMany(t => t.Users)
.Map(x => x.MapKey("RedGroupId"))
.WillCascadeOnDelete(false);
}
I am getting the following runtime error:
Users: FromRole: NavigationProperty 'Users' is not valid. Type 'RedGroup' of FromRole 'User_RedGroup_Target' in AssociationType 'User_RedGroup' must exactly match with the type 'GreenGroup' on which this NavigationProperty is declared on.
Afraid I'm stumped on how to set up this.
How can I set up the EntityFramework mappings to allow a Table per Type hierarchy?
I created a context without your mappings, but with a much simpler configuration and everything appeared to create OK:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Group>().ToTable("Groups");
modelBuilder.Entity<RedGroup>().ToTable("RedGroups");
modelBuilder.Entity<GreenGroup>().ToTable("GreenGroups");
}
I've noticed that you've defined [User].HasOptional(t => t.RedGroup), but the RedGroupId field on User is defined as int and not int? - perhaps this is related?
public class User {
int? RedGroupId { get; protected set; } // int -> int?
RedGroup RedGroup { get; protected set; } // virtual missing, but shouldn't matter
// Other properties
}
If RedGroup is required, try using .HasRequired instead.
The Customer can have only one Language. I don't find the right way to create the key. When I get an object Customer the property LanguageId has a content but not the property Language. I use EF 6.1
This Language object will be use in other object.
I did this :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Configurations.Add(new CustomerMap());
modelBuilder.Configurations.Add(new LanguageMap());
}
public class Customer
{
public int CustomerID { get; set; }
public string Code { get; set; }
public int LanguageId { get; set; }
[ForeignKey("LanguageId")]
public Language Language { get; set; }
}
public class CustomerMap : EntityTypeConfiguration<Customer>
{
public CustomerMap()
{
this.HasKey(t => t.CustomerID);
// Properties
this.Property(t => t.CustomerID).IsRequired();
this.Property(t => t.Code).IsRequired();
// Table & Column Mappings
this.ToTable("Customer");
this.Property(t => t.CustomerID).HasColumnName("CustomerID");
this.Property(t => t.Code).HasColumnName("Code");
}
}
public class Language
{
public int LanguageID { get; set; }
public string Code { get; set; }
}
public class LanguageMap : EntityTypeConfiguration<Language>
{
public LanguageMap()
{
this.HasKey(t => t.LanguageID);
this.Property(t => t.Code).IsRequired();
}
}
Update (Language will be used in other object)
You can achieve one to one with two options, but first you have to remove the foreign key value in the principal. Principal means that the record must exist first, that's why this entity doesn't need to have foreign key value, just foreign key reference.
Remove this code.
public int LanguageId { get; set; }
[ForeignKey("LanguageId")]
First. After removing above code add this configuration.
modelBuilder.Entity<Customer>()
.HasRequired(a => a.Language)
.WithRequiredPrincipal();
Second, also add the foreign key reference on dependent (Language).
public Customer Customer { get; set; }
Then mention the principal reference in WithRequiredPrincipal.
modelBuilder.Entity<Customer>()
.HasRequired(a => a.Language)
.WithRequiredPrincipal(x => x.Customer);
update
To load language you can do it with lazy loading by adding virtual keyword (the context configuration must also enable lazy loading and proxy).
public virtual Language Language { get; set; }
Or do with eager loading.
var customerID = 5;
var customer = db.Set<Customer>().Include(c => c.Language)
.FirstOrDefault(c => c.CustomerID == customerID);
I have an entity which has a self reference such that a Member can have a Witness who has to be a member and may also have a Reference who has to be a member. I modeled this as follows;
public class Member
{
public int Id { get; set; }
//omitted for brevity
public int? WitnessId { get; set; }
public virtual Member Witness { get; set; }
public int? ReferenceId { get; set; }
public virtual Member Reference { get; set; }
}
When I run the update-database on package manager console, I get the following error:
"XXX.Client.Entities.Member' and 'XXX.Client.Entities.Member'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations."
Any idea on how this can be resolved?
Try to define relationship with fluent api this way (works for me):
modelBuilder.Entity<Member>().HasKey(x => x.Id);
modelBuilder.Entity<Member>().HasOptional(x => x.Witness)
.WithMany()
.HasForeignKey(m => m.WitnessId);
modelBuilder.Entity<Member>().HasOptional(x => x.Reference)
.WithMany()
.HasForeignKey(m => m.ReferenceId);
This looks to be working:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Member>().
HasOptional(e => e.Witness).
WithMany().
HasForeignKey(m => m.WitnessID);
modelBuilder.Entity<Member>().
HasOptional(e => e.Reference).
WithMany().
HasForeignKey(m => m.ReferenceID);
base.OnModelCreating(modelBuilder);
}
This will also work for those of us who prefer to have things in the class deriving from the EntityTypeConfiguration
class MemberEntityConfiguration : EntityTypeConfiguration<Member>
{
public MemberEntityConfiguration()
{
HasKey(x => x.Id);
HasOptional(x => x.Witness).WithMany().HasForeignKey(m => m.WitnessId);
HasOptional(x => x.Reference).WithMany().HasForeignKey(m => m.ReferenceId);
}
}