Why doesn't EF core code first like my composite key? - entity-framework-core

public class UserDocuments
{
[Key]
[StringLength(30)]
public string UserID { get; set; } = string.Empty;
[Key]
public Guid DocumentID { get; set; } = Guid.Empty;
public UserType UserType { get; set; } = UserType.None;
public int Version { get; set; } = -1;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserDocuments>()
.HasKey(x => new { x.UserID, x.DocumentID });
}
public static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<UserDocuments>("UserDocuments");
return builder.GetEdmModel();
}
The detail error message I get is
"The entity set 'UserDocuments' is based on type
'DocServer2.Shared.Models.UserDocuments' that has no keys defined."
I cannot figure what is wrong with my code; I checked related entries here on SO, but cannot see a difference. I don't think that should be the case, but is it a problem to mix string and Guid in a composite key?
Edit [08/07/22]
I need to add [key] annotations to the two properties of UserDocuments that make up the key.

Related

How to define which foreign key navigation property should use?

I have two model classes
Registration
public class Registration
{
// PK
public int Id { get; private set; }
// FK's
public string AspNetUsersId { get; private set; }
public int TimetableId { get; private set; }
// EF Navigation properties
public Timetable Timetable { get; set; }
public ApplicationUser ApplicationUser { get; set; }
// EF ctor
private Registration()
{
}
// public ctor
public Registration(string aspNetUsersId, int timetableId)
{
AspNetUsersId = aspNetUsersId;
TimetableId = timetableId;
}
}
ApplicationUser
public class ApplicationUser : IdentityUser
{
// some not relevant columns
}
When I try to execute this code
var registrationExists = _context.Registrations
.Where(x => x.AspNetUsersId == user.Id
&& x.TimetableId == timetable.Id)
.SingleOrDefault();
I got an error
Microsoft.Data.SqlClient.SqlException: 'Invalid column name
'ApplicationUserId'.
I think that is because EF is trying to find (by default convention) Registration.ApplicationUserId column in database because of ApplicationUser navigation property. But the property (and sql column) is named AspNetUsersId and not ApplicationUserId. How can I override this naming in model builder (dbContext) ?
I tried something like
modelBuilder.Entity<Registration>(b =>
{
b.HasAlternateKey(x => x.AspNetUsersId);
b.HasOne(n => n.ApplicationUser);
});
but I did not figure, how to map it together.
You can write an annotation on the fields, e.g.
// PK
[Key]
public int Id { get; private set; }
// FK's
[ForeignKey(nameof(AspNetUsersId))]
public string AspNetUsersId { get; private set; }
[ForeignKey(nameof(TimetableId ))]
public int TimetableId { get; private set; }
Now, the property the [key] is the primary key and the properties with [ForeignKey()] are the FK's.
You can also use the modelBuilder:
modelBuilder.Entity<Registration>(
.HasAlternateKey(x => x.AspNetUsersId)
.HasOne(n => n.ApplicationUser)
.WithMany()
.HasForeignKey(f => f.AspNetUsersId);
https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#single-navigation-property-1)
https://entityframeworkcore.com/model-relationships

Entity Framework Core shared table with cascade delete

I try to create the following database design with EF Core (code-first)
Entity "Recipe" can have a list of type "Resource"
Entity "Shop" can have a single "Resource"
Entity "InstructionStep" can have a list of type "Resource"
If I delete a resource from the "Recipe", "InstructionStep" (collections) or from the "Shop" (single-property) then the corresponding "Resource" entity should be also deleted. (Cascade Delete)
I already tried several things with and without mapping tables but none of my approach was successful.
Another idea was to have a property "ItemRefId" in the "Resource" entity to save the "RecipeId/ShopId/InstructionStepId" but I don't get it to work...
Example Classes:
public class Recipe
{
public int RecipeId { get; set; }
public string Title { get; set; }
public ICollection<RecipeResource> Resources { get; set; } = new List<RecipeResource>();
}
public class Shop
{
public int ShopId { get; set; }
public string Title { get; set; }
public Resource Logo { get; set; }
}
public class Resource
{
public int ResourceId { get; set; }
public string Path { get; set; }
public int ItemRefId { get; set; }
}
public class InstructionStep
{
public string InstructionStepId { get; set; }
public string Title { get; set; }
public ICollection<RecipeResource> Resources { get; set; } = new List<RecipeResource>();
}
Any suggestions? Many thanks in advance.
That's not cascade delete. Cascade delete would be when a Recipe is deleted, all of the related Resources are deleted as well.
In EF Core 3, you can use Owned Entity Types for this. The generated relational model is different from what you are proposing, in that Recipe_Resource and InstructionStep_Resource will be seperate tables, and Shop.Logo will be stored in columns on the Shop table. But that's the correct relational model. Having one Resource table with some rows referencing a Recipe and some rows referencing an InstructionStep is a bad idea.
This scenario is sometimes called a "Strong Relationship" where the identity of the related entity is dependent on the main entity, and should be implemented in the relational model by having the the Foreign Key columns be Primary Key columns on the dependent entity. That way there's no way remove a Recipe_Resource without deleting it.
eg
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
namespace EfCore3Test
{
public class Recipe
{
public int RecipeId { get; set; }
public string Title { get; set; }
public ICollection<Resource> Resources { get; } = new List<Resource>();
}
public class Shop
{
public int ShopId { get; set; }
public string Title { get; set; }
public Resource Logo { get; set; }
}
public class Resource
{
public int ResourceId { get; set; }
public string Path { get; set; }
public int ItemRefId { get; set; }
}
public class InstructionStep
{
public string InstructionStepId { get; set; }
public string Title { get; set; }
public ICollection<Resource> Resources { get; } = new List<Resource>();
}
public class Db : DbContext
{
public DbSet<Recipe> Recipes { get; set; }
public virtual DbSet<Shop> Shops { get; set; }
public virtual DbSet<InstructionStep> InstructionSteps { get; set; }
private static readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddFilter((category, level) =>
category == DbLoggerCategory.Database.Command.Name
&& level == LogLevel.Information).AddConsole();
});
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLoggerFactory(loggerFactory)
.UseSqlServer("Server=.;database=EfCore3Test;Integrated Security=true",
o => o.UseRelationalNulls());
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Shop>().OwnsOne(p => p.Logo);
modelBuilder.Entity<InstructionStep>().OwnsMany(p => p.Resources);
modelBuilder.Entity<Recipe>().OwnsMany(p => p.Resources);
}
}
class Program
{
static void Main(string[] args)
{
using var db = new Db();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var r = new Recipe();
r.Resources.Add(new Resource() { ItemRefId = 2, Path = "/" });
db.Recipes.Add(r);
db.SaveChanges();
r.Resources.Remove(r.Resources.First());
db.SaveChanges();
var s = new Shop();
s.Logo = new Resource { ItemRefId = 2, Path = "/" };
db.Shops.Add(s);
db.SaveChanges();
s.Logo = null;
db.SaveChanges();
}
}
}

EF one-to-one relation not creates

I'll try create one-to-one relation using EF and Fluent API.
First class:
public class Game
{
public int Id { get; set; }
public Guid Token { get; set; }
public string Player { get; set; }
public virtual Field Field { get; set; }
public virtual ICollection<Move> Moves { get; set; }
public GameStatus Status { get; set; }
public DateTime StartTime { get; set; }
public DateTime? EndTime { get; set; }
public PlayerCode Winner { get; set; }
public Game()
{
Status = GameStatus.NoteDone;
StartTime = DateTime.UtcNow;
Winner = PlayerCode.None;
Field = new Field {Game = this};
Token = Guid.NewGuid();
}
}
Secong class:
public class Field : IEntity
{
public int Id { get; set; }
public virtual Game Game { get; set; }
public string CellsString { get; set; }
}
And configure relations in context
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Game>()
.HasRequired<Field>(g => g.Field)
.WithRequiredDependent(f => f.Game);
base.OnModelCreating(modelBuilder);
}
But after this relation in DB is not created. Tables look like this
I try many variations of Fluent configuration, but no one works for me. Where i do mistake?
You can specify a mapping for foreign key if you don't wish to add it as a property to your entity class.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Game>()
.HasRequired(g => g.Field)
.WithRequiredPrincipal(f => f.Game)
.Map(m => m.MapKey("GameId"));
}
You probably meant WithRequiredPrincipal, not WithRequiredDependent since you probably want that foreign key to be in the Field table.

Entity framework replaces delete+insert with an update. How to turn it off

I want to remove a row in database and insert it again with the same Id, It sounds ridiculous, but here is the scenario:
The domain classes are as follows:
public class SomeClass
{
public int SomeClassId { get; set; }
public string Name { get; set; }
public virtual Behavior Behavior { get; set; }
}
public abstract class Behavior
{
public int BehaviorId { get; set; }
}
public class BehaviorA : Behavior
{
public string BehaviorASpecific { get; set; }
}
public class BehaviorB : Behavior
{
public string BehaviorBSpecific { get; set; }
}
The entity context is
public class TestContext : DbContext
{
public DbSet<SomeClass> SomeClasses { get; set; }
public DbSet<Behavior> Behaviors { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<SomeClass>()
.HasOptional(s => s.Behavior)
.WithRequired()
.WillCascadeOnDelete(true);
}
}
Now this code can be executed to demonstrate the point
(described with comments in the code below)
using(TestContext db = new TestContext())
{
var someClass = new SomeClass() { Name = "A" };
someClass.Behavior = new BehaviorA() { BehaviorASpecific = "Behavior A" };
db.SomeClasses.Add(someClass);
// Here I have two classes with the state of added which make sense
var modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// They save with no problem
db.SaveChanges();
// Now I want to change the behavior and it causes entity to try to remove the behavior and add it again
someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" };
// Here it can be seen that we have a behavior A with the state of deleted and
// behavior B with the state of added
modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// But in reality when entity sends the query to the database it replaces the
// remove and insert with an update query (this can be seen in the SQL Profiler)
// which causes the discrimenator to remain the same where it should change.
db.SaveChanges();
}
How to change this entity behavior so that delete and insert happens instead of the update?
A possible solution is to make the changes in 2 different steps: before someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" }; insert
someClass.Behaviour = null;
db.SaveChanges();
The behaviour is related to the database model. BehaviourA and B in EF are related to the same EntityRecordInfo and has the same EntitySet (Behaviors).
You have the same behaviour also if you create 2 different DbSets on the context because the DB model remains the same.
EDIT
Another way to achieve a similar result of 1-1 relationship is using ComplexType. They works also with inheritance.
Here an example
public class TestContext : DbContext
{
public TestContext(DbConnection connection) : base(connection, true) { }
public DbSet<Friend> Friends { get; set; }
public DbSet<LessThanFriend> LessThanFriends { get; set; }
}
public class Friend
{
public Friend()
{Address = new FullAddress();}
public int Id { get; set; }
public string Name { get; set; }
public FullAddress Address { get; set; }
}
public class LessThanFriend
{
public LessThanFriend()
{Address = new CityAddress();}
public int Id { get; set; }
public string Name { get; set; }
public CityAddress Address { get; set; }
}
[ComplexType]
public class CityAddress
{
public string Cap { get; set; }
public string City { get; set; }
}
[ComplexType]
public class FullAddress : CityAddress
{
public string Street { get; set; }
}

Oracle ODP.Net and EF CodeFirst - edm.decimal error

I have the following simple entity:
public class Something{
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public int ID { get; set; }
public string NAME { get; set; }
public int STATUS { get; set; }
}
As you can see, I do not want the ID is generated from the database but I'm going to enter manually. This my DbContext class:
public class MyCEContext : DbContext {
...
public DbSet<Something> Somethings { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
string dbsch = "myce";
modelBuilder.Entity<Something>().ToTable("SOMETHING", dbsch);
}
}
There is nothing special here. But this code fails:
using (MyCEContext ctx = new MyCEContext()) {
Something t = new Something();
t.ID= 1;
t.NAME = "TEST";
t.STATUS = 100;
ctx.Somethings.Add(t);
ctx.SaveChanges();
}
This is the error:
The specified value is not an instance of type 'Edm.Decimal'
In general, allways EF try to send a value to an int primary key field, I get the edm.decimal error.
Any help?
As I commented on previous answer, I've found better solution, it is strange, but it works
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<TestEntity>().ToTable("TESTENTITY", "SCHEMENAME");
modelBuilder.Entity<TestEntity>().Property(p => p.Id).HasColumnName("ID").HasColumnType("INT");
modelBuilder.Entity<TestEntity>().Property(p => p.TestDateTime).HasColumnName("TESTDATETIME");
modelBuilder.Entity<TestEntity>().Property(p => p.TestFloat).HasColumnName("TESTFLOAT");
modelBuilder.Entity<TestEntity>().Property(p => p.TestInt).HasColumnName("TESTINT");
modelBuilder.Entity<TestEntity>().Property(p => p.TestString).HasColumnName("TESTSTRING");
}
and TestEntity looks like this
public class TestEntity
{
public int Id{ get; set; }
public string TestString { get; set; }
public int TestInt { get; set; }
public float TestFloat { get; set; }
public DateTime TestDateTime { get; set; }
}