I have the following model:
public class User
{
public Guid Id {get;set;}
public string Username {get;set;}
public string Address Useraddress {get;set;}
}
public class Address
{
public string Street {get;set;}
public string Zipcode {get;set;}
}
I want to save the data in Useraddress to the same User table. So I added an OwnsOne configuration to the context builder.
class UserEntityTypeConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.HasKey(x => x.Id);
builder.OwnsOne(x => x.UserAddress);
}
}
When I run the migrations tool then it all seems to be fine. This is the relevant part from the migrations script that is generated:
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<Guid>(nullable: false),
Username = table.Column<string>(nullable: false),
Useraddress_Street = table.Column<string>(nullable: true),
Useraddress_Zipcode = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
Then when I later on try to add a User:
await _dbContext.Users.AddAsync(user);
await _dbContext.SaveChangesAsync();
I then get the following error:
The entity of 'User' is sharing the table 'Users' with 'User.Useraddress#Address', but there is no entity of this type with the same key value that has been marked as 'Added'
Is there something that I'm doing wrong?
PS.
I'm using Entity Framework Core 2.0.
EF Core 2.0 by default creates a primary key as a shadow property for the owned entity since it supports table splitting, therefore, the value of the UserAddress property in the User instance cannot be null and must be defined.
var user = new User
{
Id = Guid.NewGuid(),
Username = "...",
UserAddress = new Address
{
Street = "...",
Zipcode = "..."
}
};
await _dbContext.Users.AddAsync(user);
await _dbContext.SaveChangesAsync();
If you want the values of the owned entity to be nulls then just define a default instance, i.e.:
var user = new User
{
Id = Guid.NewGuid(),
Username = "...",
UserAddress = new Address()
};
You can read more about owned entity implicit keys here: https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities#implicit-keys
The presented solution does not work in EF Core 5.0 for me. I've added constructor into the Address class
public Address()
{
Line1 = string.Empty;
}
(Just at least one field should not be null).
Now it works fine for new root entities. But I still fix existing entities in the database
UPDATE RootEntityTable SET Address_Line1 = '' WHERE Id IN (...)
Related
I want to seed data with EfCore to my Owned Entities who can be nullable
Entities:
public class RootEntity
{
protected RootEntity() { }
public Guid Id { get; set; }
public OwnedEntityLevel1? OwnedEntityLevel1 { get; set; } // can be nullable
}
public class OwnedEntityLevel1
{
public Guid Id { get; set; }
}
Model configuration for DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<RootEntity>(b =>
{
b.OwnsOne(x => x.OwnedEntityLevel1, ob =>
{
ob.HasData(RootEntity.All.Select(x => new
{ x.OwnedEntityLevel1?.Id, RootEntityId = x.Id }));
});
b.HasData(RootEntity.All.Select(x => new { x.Id }));
});
}
When i try to create my migration with:
dotnet ef migrations add Initial --context NullableObjectDbContext -o Migrations/NullableObject
i receive the error:
The seed entity for entity type 'OwnedEntityLevel1' cannot be added because no value was provided for the required property 'Id'.
The message oviously is correct. But i do not understand if you could seed nullable objects with .HasData somehow?
The data i am trying to seed:
public static RootEntity PredefinedEntity11 { get; } =
new(
Guid.Parse("96e1d442-bdd0-4c6f-9d01-624b27abbac3"),
new OwnedEntityLevel1
{
Id = Guid.Parse("8f8eea73-0b43-412a-b0aa-a9338db6e067")
}
);
public static RootEntity PredefinedEntity12 { get; } =
new(
Guid.Parse("aae51dac-016e-472e-ad51-2f09f8cb9fbb"),
null! // When i add this the migration fails with The seed entity for entity type 'OwnedEntityLevel1' cannot be added because no value was provided for the required property 'Id'
);
public static IReadOnlyList<RootEntity> All { get; } =
new List<RootEntity>(new[] { PredefinedEntity11, PredefinedEntity12 }).AsReadOnly();
I my normal program flow i can add nullable objects without a problem:
var ctx = new NullableObjectDbContext();
var rootEntity = new RootEntity(Guid.NewGuid(), null);
ctx.Add(rootEntity);
ctx.SaveChanges();
I have created a minimal reproducible example here: https://github.com/enterprisebug/EfCoreHasDataNestedOwnedTypes/tree/main/EfCoreHasDataNestedOwnedTypes/NullableObject
Model data seeding with anonymous types matches properties by both name and type.
In your case, even though the seeding type has property called Id, its type is different from the type of the Id property of the seeded entity (Nullable<Guid> inferred from ?. operator vs Guid), hence is not mapped and is generating the confusing error message.
new
{
x.OwnedEntityLevel1?.Id, // Guid? Id
RootEntityId = x.Id // Guid RootEntityId
}
The solution is to generate and populate a Guid Id property in the anonymous type by first filtering out null objects, e.g. (null forgiving operator is used to suppress the NRT warning):
ob.HasData(RootEntity.All
.Where(x => x.OwnedEntityLevel1 != null)
.Select(x => new
{
x.OwnedEntityLevel1!.Id, // Guid Id
RootEntityId = x.Id // Guid RootEntityId
}));
I have the following data model:
public class Foo
{
public Foo(int barId)
{
BarId = barId;
}
private int BarId;
public Bar Bar { get; private set; }
}
public class FooTypeConfig : IEntityTypeConfiguration<Foo>
{
public void Configure(EntityTypeBuilder<Foo> builder)
{
builder.HasOne(x => x.Bar)
.WithMany()
.HasForeignKey("BarId");
}
}
public class Bar
{
public int Id { get; private set; }
}
This works great and according to my expectations, I have a Foo table containing Id and BarId. My private field BarId and my Bar property are also correctly materialized when reading Foo from the database.
The problem is that I would like to find a way to name my private field, and choose a different name for my database column. I would like to name my property _barId and still choose BarId as my column name in my database.
Is this possible?
I have tried renaming the field in my Foo class and specifying my (now non-conventionally-named) foreign key _barId in my EntityTypeConfiguration
builder.HasOne(x => x.Bar).WithMany().HasForeignKey("_barId");
But this resulted in EF still generating a BarId column, without using it as foreign key to the Bar table...
migrationBuilder.CreateTable(
name: "Foos",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
BarId = table.Column<int>(nullable: true),
_barId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Foos", x => x.Id);
table.ForeignKey(
name: "FK_Foos_Bars__barId",
column: x => x._barId,
principalTable: "Bars",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
First off, EF maps database columns and FKs to entity properties, not fields. The properties can be real or as in your case - shadow.
So the following line:
builder.HasOne(x => x.Bar).WithMany().HasForeignKey("BarId");
maps the Bar -> Foo relationship FK to a Foo shadow property called BarId and should stay as it is.
You use the Property method to configure the property type, backing field, column name, type and other attributes. For instance:
builder.Property<int>("BarId") // or int? etc.
.HasField("_barId")
.HasColumnName("BarId"); // or BazId or whatever you like
Just make sure you use one and the same property name when defining it and when specifying the FK. You can also use Entry(entity).Property(propertyName) to get/set the value, mark it as modified etc. as well as EF.Property(entity, propertyName) to access it inside LINQ to Entities queries.
When I save entity which has navigation property which I use as foreign key:
this.HasRequired<Role>(c => c.Role).WithMany().Map(c => c.MapKey("role_id"));
I set only foreign key property of this navigation property (I get it from web page) thereby other properties of this navigation property are empty, but they have required restrictions:
this.Property(c => c.RoleName).IsRequired();
It's the reason why I get "dbentityvalidationexception" exception with error "field is required".
Is it possible to solve this problem by somehow?
Or I must get full entity for that navigation property from DB, set navigation property of entity which I save and then save my initial entity (It works now, but it doesn't look like good solution)?
Thanks in advance.
This is MS MVC action where I handle the model:
[HttpPost]
public async Task<ActionResult> AddAsync(Staff staff)
{
await staffService.InsertAsync(staff);
return RedirectToAction("Index");
}
and that part of view where I set the property:
<dt>
#Html.Label("Role")
</dt>
<dd>
#Html.DropDownListFor(_=>_.Role.Id, new SelectList(ViewBag.Roles, "Id", "RoleName"), "- Please select a Role -")
</dd>
this is the type of model "Staff"
public class Staff
{
public Staff()
{
Name = new Name();
}
public int Id { get; set; }
public Name Name { get; set; }
...
public virtual Role Role { get; set; }
...
}
I have found more or less good solution for that problem.
I have set all navigation property for which I have only foreign key property as
EntityState.Unchanged.
Now I have this method for saving only specific entity (Staff)
public virtual Staff InsertStaff(Staff entity)
{
context.Entry(entity.Role).State = EntityState.Unchanged;
context.Entry(entity.Maneger).State = EntityState.Unchanged;
SetStaffNavigationPropertiesUnchanged(entity);
return dbSet.Add(entity);
}
and this for saving full graph (in base generic class):
public virtual TEntity InsertGraph(TEntity entity)
{
return dbSet.Add(entity);
}
using "EntityState.Unchanged" (I will show in simplest way) lets me saving Staff when Role has only foreign key property filled (Role has RoleName required property)
using (ClinchContext context = new ClinchContext())
{
var role = new Role { Id = 1 };
Staff staff = context.Staffs.Add(new Staff
{
Name = new Name { Firstname = "FN", Surname = "S", Patronymic = "P" },
Email = "E",
Login = "L",
IsDeleted = false,
Role = role,
Maneger = null//nullable for now
});
context.Entry(staff.Role).State = EntityState.Unchanged;
context.SaveChanges();
}
If someone has more proper solution I would be happy to know, Thank you.
I'm early beginner in ASP.NET, and I'm trying to build user system using Identity framework in ASP.NET-MVC project.
I want to have "Admin" user entity inherit from base entity "applicationUser" which is created by default in my models (with my minor changes):
public class ApplicationUser : IdentityUser
{
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
}
public class Admin: ApplicationUser
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int AdID;
}
I will also add 2 other inheritance from applicationUser, I want to have separeted models for different users to store different information in their database fileds and let all of them login to my site.
In startup I'm trying to initially add admin user using this code (taken somewhere in stackoverflow answers):
public static async Task CreateRoles(SchoolContext context, IServiceProvider serviceProvider)
{
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
// First, Creating User role as each role in User Manager
List<IdentityRole> roles = new List<IdentityRole>();
roles.Add(new IdentityRole { Name = "Student", NormalizedName = "STUDENT" });
roles.Add(new IdentityRole { Name = "ADMIN", NormalizedName = "ADMINISTRATOR" });
roles.Add(new IdentityRole { Name = "Professor", NormalizedName = "PROFESSOR" });
//Then, the machine added Default User as the Admin user role
foreach (var role in roles)
{
var roleExit = await roleManager.RoleExistsAsync(role.Name);
if (!roleExit)
{
context.Roles.Add(role);
context.SaveChanges();
}
}
await CreateUser(context, userManager);
}
private static async Task CreateUser(SchoolContext context, UserManager<ApplicationUser> userManager)
{
var adminUser = await userManager.FindByEmailAsync("alpha#lms.com");
if (adminUser != null)
{
if (!(await userManager.IsInRoleAsync(adminUser, "ADMINISTRATOR")))
await userManager.AddToRoleAsync(adminUser, "ADMINISTRATOR");
}
else
{
var newAdmin = new applicationUser()
{
UserName = "alpha#lms.com",
Email = "alpha#lms.com",
LastName = "Admin",
FirstMidName = "Admin",
};
var result = await userManager.CreateAsync(newAdmin, "password123;");
if (!result.Succeeded)
{
var exceptionText = result.Errors.Aggregate("User Creation Failed
- Identity Exception. Errors were: \n\r\n\r",
(current, error) => current + (" - " + error + "\n\r"));
throw new Exception(exceptionText);
}
await userManager.AddToRoleAsync(newAdmin, "ADMINISTRATOR");
}
}
But when I try to do it, I receive an exception:
"Cannot insert the value NULL into column 'Discriminator', table 'aspnet-University; column does not allow nulls. INSERT fails.
The statement has been terminated."
If I try to change variable Admin type on "new Admin" instead of "new applicationUser", I receive another exception:
The entity type 'Admin' was not found. Ensure that the entity type has been added to the model.
I know that this question is about basics of identity framework; I do not understand them well yet; I've just began to understand it's prinicples and I don't know how to handle my problem.
My guess that I need to create New UserManager, which will be able to manipulate instances which inherit from basic applicationUser model. I will be happy if you recommend me relevant resources on this topic and help me solve my problem.
In case if anyone will have this question - I've decided not to use inheritance from ApplicationUser, because it obviously was wrong approach, instead and inherit all custom user models from IdentityUser class. To make this work I've added to StartUp Configure services this lines:
services.AddIdentity <IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<YourContext>()
.AddDefaultTokenProviders();
My user model declared like this:
public class Admin : IdentityUser
{
}
And in context file I have this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<IdentityUser>(i => {
i.ToTable("Users");
i.HasKey(x => x.Id);
});
modelBuilder.Entity<IdentityRole<>(i => {
i.ToTable("Role");
i.HasKey(x => x.Id);
});
}
With this code I can use Identity's UserManager and RoleManager without additional changes.
I have an issue while mapping results of a stored procedure (dbo.sp_Get_User) to an existing UserEntity entity.
I didn't want to user db table column names in my project directly. So I have UserEntity as follows:
public class UserEntity
{
public UserEntity()
{
}
public int UserKey { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
And my mapper maps all the columns of table to this entity as follows:
public class UserEntityMapper : EntityTypeConfiguration<UserEntity>
{
public UserEntityMapper()
{
ToTable("tbl_User");
HasKey(m => m.UserKey);
Property(p => p.UserKey).HasColumnName("User_KEY");
Property(p => p.FirstName).HasColumnName("FIRST_NAME");
Property(p => p.LastName).HasColumnName("LAST_NAME");
}
}
Now I have a condition where I need to use a stored procedure to get the user data (based on some dynamic queries and all, I have to use this stored procedure for sure)
So I am trying to execute the stored procedure as follows:
SqlParameter temp1 = new SqlParameter("#EffecitveDate", DateTime.Parse("01/01/2012"));
SqlParameter temp2 = new SqlParameter("#User_Key", id);
IEnumerable<UserEntity> usrEnt = context.Database.SqlQuery<UserEntity>("sp_Get_User #EffecitveDate, #User_Key", temp1, temp2);
But I am getting following error while executing the above line:
The data reader is incompatible with the specified 'UserEntity'. A member of the type, 'UserKey', does not have a corresponding column in the data reader with the same name.
I am really stuck on this please suggest. Thanks