Relations between model class and returning data via LINQ - entity-framework

I'm building REST API server side and I have a problem with fetching data from a database by LINQ. I don't know if my model classes and relations are built in proper way but I can't get data from database by LINQ query.
I'm using .NET core (.NETCoreApp 1.1).
My Model:
User class :
public class User
{
[Key]
public int IDUser { get; set; }
[Required]
public string Forename { get; set; }
[Required]
public string Name { get; set; }
public string AvatarPath { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string Password { get; set; }
public User CreatedBy { get; set; }
public DateTime CreatedAt { get; set; }
public List<AccessCard> Cards { get; set; }
}
AccessCard class :
public class AccessCard
{
[Key]
public int IDAccessCard { get; set; }
[Required]
public string CardNumber { get; set; }
[Required]
public bool IsValid { get; set; }
public User User { get; set; }
public User AddedBy { get; set; }
[Required]
public DateTime CreatedAt { get; set; }
}
DBcontext class :
public class DBContext : DbContext
{
public DBContext(DbContextOptions<DBContext> options)
: base(options)
{}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AccessCard>()
.HasOne(i => i.User).WithMany(u => u.Cards);
base.OnModelCreating(modelBuilder);
}
public DbSet<AccessCard> AccessCards { get; set; }
public DbSet<User> Users { get; set; }
}
UsersController class :
[Produces("application/json")]
[Route("api/[controller]")]
public class UsersController : Controller
{
private readonly DBContext _context;
#region CONSTRUCTOR
public UsersController(DBContext context)
{
_context = context;
}
#endregion
#region HTTP GET
// GET: api/users
[HttpGet]
public async Task<IActionResult> GetUsers(string cardNr)
{
if (String.IsNullOrEmpty(cardNr))
{
var users = await _context.Users.ToListAsync();
if (users.Any())
{
return Json(users);
}
else
{
return NotFound();
}
}
else
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.Cards.Exists(c => c.CardNumber.Equals(cardNr)));
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
}
//GET: api/users/1
[HttpGet("{id}")]
public async Task<IActionResult> GetUserByID(Int32 id)
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.IDUser == id);
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
#endregion
}
I bulit my database with EntityFramework core with code first approach. I used Package Manager Console to utilize Add-Migration and then Update-Database commands to create the database. Migrations created successfully my DB. Database server is local MS SQL-Server.
What I want to do is to get Users by its AccessCard number. So I'm trying this in the browser :
GET api/users?cardnr=qwe
which dosn't return me anything .... I added manually Users and AccessCards to database with correct foreign keys so Users in my DB has some AccessCards on 100%. Parameter string cardNrin my GetUsers method correctly gets populated with value (qwe e.g.).
When I use this :
GET api/users
GET api/users/1
GET api/accesscards
API successfully returns me data so my controller works correctly. I get recpectively all Users, User by ID, all AccessCards.
When I try to debug the UsersController and I set a trap on var user = await _context.Users.FirstOrDefaultAsync(u => u.Cards.Exists(c => c.CardNumber.Equals(cardNr))); line the code just keep exit from the method and doesn't return me anything.
I don't know if my relation between model classes is built correctly. Is this get the job done correctly ? :
modelBuilder.Entity<AccessCard>()
.HasOne(i => i.User).WithMany(u => u.Cards);
Do I have to add anything else to by DBcontext class to establish correct relation between User and AccessCard classes ? Basically public List<AccessCard> Cards { get; set; } collection should return all AccessCards for a given User in my case.
Please help.
Thanks

Related

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();
}
}
}

One API call to retrieve all items in the model

I created a simple web api using Net Core 2.2. I have this api controller below, that gets one particular dungeon.
It is returning a dungeon as JSON, but it's not returning the MonsterList associated with the dungeon.
So this is my controller:
// GET: api/DungeonLists/5
[HttpGet("{id}")]
public async Task<ActionResult<DungeonList>> GetDungeonList(Guid id)
{
var dungeonList = await _context.DungeonList.FindAsync(id);
if (dungeonList == null)
{
return NotFound();
}
return dungeonList;
}
And here is my model for the Dungeon. As you can see, it has a MonsterList.
public partial class DungeonList
{
public DungeonList()
{
MonsterList = new HashSet<MonsterList>();
}
public Guid DungeonId { get; set; }
public string DungeonName { get; set; }
public string DungeonDesc { get; set; }
public string MapArea { get; set; }
public bool ShowProgress { get; set; }
public bool? DungeonResumable { get; set; }
public virtual ICollection<MonsterList> MonsterList { get; set; }
}
Here is my MonsterList model:
public partial class MonsterList
{
public string MonsterId { get; set; }
public Guid DungeonId { get; set; }
public string MonsterName { get; set; }
public byte? MonsterType { get; set; }
public bool IsBossMonster { get; set; }
public virtual DungeonList Dungeon { get; set; }
}
I want the JSON to also show the list of monsters associated with the dungeon.
Is there a way to do this? Or would I need to make a separate API call?
Thanks!
You need to change your code to the following:
[HttpGet("{id}")]
public async Task<ActionResult<DungeonList>> GetDungeonList(Guid id)
{
var dungeonList = await _context.DungeonList
.Include(i => i.MonsterList)
.FirstOrDefaultAsync(p => p.Id = id);
if (dungeonList == null)
{
return NotFound();
}
return dungeonList;
}
Additionally, since you arent using LazyLoading, you dont need the [virtual] on the MonsterList collection

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; }
}

My collections / model look fine after saving, but when loading entity I do not get the data back out. What am I doing wrong?

Here is my source code for my model:
public class User
{
public User()
{
GUID = Guid.NewGuid();
Account = new Account();
Location = new Location();
}
public long UserID { get; set; }
public Guid GUID { get; set; }
public string DisplayName { get; set; }
public Location Location { get; set; }
public virtual Account Account { get; set; }
}
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasKey(x => x.UserID);
}
}
[ComplexType]
public class Location
{
[MaxLength(2)]
public string CountryCode { get; set; }
[MaxLength(2)]
public string StateCode { get; set; }
public string City { get; set; }
}
public class Account
{
public Account()
{
if (EmailAddresses == null) EmailAddresses = new Collection<EmailAddress>();
}
[ForeignKey("User")]
public long AccountID { get; set; }
public ICollection<EmailAddress> EmailAddresses { get; set; }
public virtual User User { get; set; }
}
public class AccountConfiguration : EntityTypeConfiguration<Account>
{
public AccountConfiguration()
{
HasKey(x => x.AccountID);
HasMany(x => x.EmailAddresses).WithRequired(x => x.Account);
}
}
public class EmailAddress
{
[Key]
public string Email { get; set; }
public EmailTypes Type { get; set; }
public long AccountID { get; set; }
public virtual Account Account { get; set; }
}
public class EmailAddressConfiguration : EntityTypeConfiguration<EmailAddress>
{
public EmailAddressConfiguration()
{
HasKey(x => x.Email);
HasRequired(x => x.Account).WithMany(x => x.EmailAddresses).HasForeignKey(x => x.AccountID);
}
}
And here is my Entity Class:
public class MyEntities : DbContext
{
public MyEntities()
{
Database.SetInitializer<MyEntities>(new DropCreateDatabaseAlways<MyEntities>());
}
public DbSet<User> Users { get; set; }
public DbSet<Account> Accounts { get; set; }
public DbSet<EmailAddress> EmailAddresses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new UserConfiguration());
modelBuilder.Configurations.Add(new AccountConfiguration());
modelBuilder.Configurations.Add(new EmailAddressConfiguration());
base.OnModelCreating(modelBuilder);
}
}
And finally my code that runs in a test console application:
static void Main(string[] args)
{
var id = CreateUser();
using (MyEntities db = new MyEntities())
{
var a = db.Users.Find(id);
var b = a.Account.EmailAddresses;
var c = db.Accounts.Find(id);
var d = db.EmailAddresses.Where(x => x.Account.AccountID == id).ToList();
}
}
private static long CreateUser()
{
using (MyEntities db = new MyEntities())
{
var u = new User();
u.DisplayName = "TEST";
u.Location.CountryCode = "US";
u.Location.StateCode = "HI";
u.Location.City = "Kauai";
EmailAddress e = new EmailAddress();
e.Email = DateTime.UtcNow.Ticks + "#microsoft.com";
e.Type = EmailTypes.Current;
u.Account.EmailAddresses.Add(e);
db.Users.Add(u);
var cnt = db.SaveChanges();
// Here I get a return of the 4 entities saved, and my model looks correct.
return u.UserID;
}
}
Once the model was saved (CreateUser), I was able to navigate the model and everything looked perfect.
The issue arises when I try to pull the data back out.
My variables:
a -- navigating to email adderess shows 0 records.
b -- this too shows 0 records in the collection.
c -- navigating to email adderess shows 0 records.
d -- here I can get email addresses (but not by navigating the model)
Your test code to access the navigation properties relies on lazy loading. But your Account.EmailAddresses collection is not marked as virtual:
public ICollection<EmailAddress> EmailAddresses { get; set; }
Navigation properties must be virtual (like your User.Account property) in order to make lazy loading possible.
As a side note: I recommend to remove the instantiation of the Account navigation property...
Account = new Account();
...from the User constructor. This is a known source for trouble:
What would cause the Entity Framework to save an unloaded (but lazy loadable) reference over existing data?
EF 4.1 Code First: Why is EF not setting this navigation property?
Instantiating Location is fine because it's a complex type and not a navigation property.

Entity Framework 4.1 : The navigation property 'BusinessUser' declared on type 'Login' has been configured with conflicting multiplicities

I am having two entities
BusinessUser { Id(PK), Name,...}
Login { BusinessUserID(PK, FK), Email, Password, etc...}
Relationship between BusinessUser and Login is one-to-zero/one.
I am having following configurations
In BusinessUser EF configuration class
this.HasOptional(bu => bu.LoginInfo)
.WithOptionalPrincipal(l => l.BusinessUser);
In Login EF configuration class
this.HasRequired(l => l.BusinessUser)
.WithOptional(bu => bu.LoginInfo);
I am getting following exception
The navigation property 'BusinessUser' declared on type 'Login' has been configured
with conflicting multiplicities.
Where I am wrong with my one-to-one/zero configuration in EF 4.1 code first.
Update 1 : Following are my class structure
public class BusinessUser {
public virtual int ID { get; set; }
public virtual int BusinessID { get; set; }
public virtual Business Business { get; set; }
public Login LoginInfo { get; set; }
}
public class Login {
public virtual int BusinessUserID { get; set; }
public virtual string Email { get; set; }
public virtual string Password { get; set; }
public BUsinessUser BusinessUserInfo { get; set; }
}
Also I am looking for bi-directional.
Your BusinessUser must have relation configured as:
this.HasOptional(bu => bu.LoginInfo)
.WithRequired(l => l.BusinessUser);
Both configuration must be same (actually only one is needed) and the first configuration is incorrect because it is trying to define 0..1 - 0..1 relation.
How have you structured your classes ? Here's a sample with a relationship one-to-one/zero defined.
The result is :
BusinessUser { Id(PK), Name,...}
Login { BusinessUserID(PK, FK), Email, Password, etc...}
public class BusinessUser
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public virtual LoginInfo LoginInfo { get; set; }
}
public class LoginInfo
{
public int BusinessUserId { get; set; }
public virtual BusinessUser BusinessUser { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
Here is the DbContext and the Initializer
public class MyContext : DbContext
{
public DbSet<BusinessUser> BusinessUsers { get; set; }
public DbSet<LoginInfo> LoginInfos { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
//We define the key for the LoginInfo table
modelBuilder.Entity<LoginInfo>().HasKey(x => x.BusinessUserId);
modelBuilder.Entity<LoginInfo>().HasRequired(bu => bu.BusinessUser);
}
}
public class MyInitializer : DropCreateDatabaseIfModelChanges<MyContext>
{
protected override void Seed(MyContext context)
{
var businessUser = new BusinessUser();
businessUser.Email = "mymail#email.com";
businessUser.Name = "My Name";
businessUser.LoginInfo = new LoginInfo(){Username = "myusername", Password ="mypassword"};
context.BusinessUsers.Add(businessUser);
context.SaveChanges();
}
}