Updating a relation between two Entity Framework entities? - entity-framework

I have two related Entity Framework 6 classes in my data layer.
public class Order
{
public int Id { get; set; }
public virtual SalesStatus SalesStatus { get; set; }
}
public class SalesStatus
{
public SalesStatus()
{
Orders = new List<Order>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Order> Orders { get; set; }
}
public class OrderVM
{
public int Id { get; set; }
public SalesStatus SalesStatus { get; set; }
}
I am using Automapper to map these to my view models and back again.
cfg.CreateMap<Order, OrderVM>()
.MaxDepth(4)
.ReverseMap();
The status entity is used to populate a drop down list.
In my method I am taking the selected value and trying to update the order record to the new selected status.
private bool SaveOrderToDb(OrderVM orderVM)
{
using (var db = new MyContext())
{
var order = AutomapperConfig.MapperConfiguration.CreateMapper().Map<OrderVM, Order>(orderVM);
order.SalesStatus = db.SalesStatuses.Find(Convert.ToInt16(orderVM.SalesStatusSelectedValue));
db.Set<Order>().AddOrUpdate(order);
db.SaveChanges();
}
return true;
}
This does not update the relationship in the database. Why? What am I missing?

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

Parent object is null when getting child objects using Entity Framework

public class AcsBatchingDbContext : DbContext
{
public DbSet<CardHolder> CardHolders { get; set; }
public DbSet<AccessCard> AccessCards { get; set; }
}
public class CardHolder
{
public int CardHolderId { get; set; }
public ICollection<AccessCard> AccessCards { get; set; };
}
public class AccessCard
{
public int AccessCardId { get; set; }
public CardHolder CardHolder { get; set; }
}
When I try to get AccessCards
using (var db = new AcsBatchingDbContext())
{
var cards = db.AccessCards.ToList();
}
Where card.CardHolder = null
Why? Why EF doesnt grab the CardHolder?
Another question:
Why this expression doesnt compile?
db.AccessCards.Include(x => x.CardHolder).ToList();
Why the only options is to use is
db.AccessCards.Include("CardHolder").ToList();
You should use include to load CardHolder entity.
(This requires using System.Data.Entity;)
db.AccessCards.Include(x => x.CardHolder).ToList();
Alternatively, you can apply Include like this;
db.AccessCards.Include("CardHolder").ToList();

How to map a table to an enumerable property using entity framework? (Database first)

Suppose I had these two classes:
public class MyClass {
public int Id { get; set; }
public IEnumerable<MyData> MyDataCollection { get; set; }
}
public class MyData {
public int DataId { get; set; }
public string Data { get; set; }
public int Id { get; set; }
}
and I had two tables in my database:
MyClasses:
Id
1
2
MyDatas:
DataId Id Data
1 - 1 - "Hello"
2 - 1 - "World"
3 - 2 - "Hello World"
How do I use entity framework to link them up, so that I can do:
using (var db = new DataContext()) {
var data = db.MyClass.Where(c => c.ID == 1).MyDataCollection;
foreach (var item in data) Console.WriteLine(item.Data);
}
I have the other data but so far I've just written [NotMapped] above the MyDataCollection property, but obviously I want to get rid of that and have it mapped. How do I (correctly) map it?
You can do it by expanding your class with appropriate navigation properties to establish the joins between them by following EF standards
public class MyClass {
public MyClass()
{
MyDataCollection = new List<MyData>();
}
public int Id { get; set; }
public virtual ICollection<MyData> MyDataCollection { get; set; }
}
public class MyData {
public int DataId { get; set; }
public string Data { get; set; }
public int Id { get; set; }
public virtual MyClass MyClass { get; set; }
}
Make sure these 2 entities are declared in the dbcontext as below
public class MyContext: DbContext
{
public MyContext(): base()
{
}
public DbSet<MyClass> MyClasses { get; set; }
public DbSet<MyData> MyDatas { get; set; }
}

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

.Net MVC 4 REST Cannot send Object

I have build a .Net Mvc 4 application and now I want to extend it with REST.
I am using the Entity Framework and I have the following problem.
My goal is to have a system where categories have a number of products and where products can belong to multiple categories.
As follows:
public class Categorie
{
[Key]
public int Id { get; set; }
[Required]
public string Naam { get; set; }
[Required]
public string Omschrijving { get; set; }
public byte[] Plaatje { get; set; }
private List<Product> producten;
public virtual List<Product> Producten
{
get { return producten; }
set { producten = value; }
}
}
public class Product
{
[Key]
public int Id { get; set; }
[Required]
public string Naam { get; set; }
[Required]
public string Omschrijving { get; set; }
[Required]
public double Prijs { get; set; }
private List<Categorie> categorien = new List<Categorie>();
public virtual List<Categorie> Categorien
{
get { return categorien; }
set { categorien = value; }
}
[Required]
public byte[] Plaatje { get; set; }
}
NOTE: There are virtual properties in there so that my entity framework creates a merging table. Normally it links all the categorie's to the products and vice versa.
And my rest looks like:
// GET api/Rest/5
public Product GetProduct(int id)
{
Product product = db.Producten.Find(id);
Product newProduct = new Product();
if (product == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
else
{
product.Categorien = null;
}
newProduct.Id = product.Id;
newProduct.Naam = product.Naam;
newProduct.Omschrijving = product.Omschrijving;
newProduct.Plaatje = product.Plaatje;
newProduct.Prijs = product.Prijs;
newProduct.Categorien = product.Categorien;
return newProduct;
}
First problem: I cannot send any product aslong as it has a categorie. I have to make it null.
Second problem: I cannot send the original product because of the first problem.
I am assuming your problem is with a circular reference during serialization, since categories reference multiple products and products reference multiple categories. One solution is to use Data Transfer Objects (DTO) instead of returning the straight entities you are using for EF. To make it easy to map your entities to the DTO's I would use AutoMapper. This is essentially what you are doing when you create an instance of newProduct in your REST API method, but AutoMapper takes the hard coding and drudgery out of mapping. Your DTO for a product would look very similar but they would not have the virtual navigation properties or the attributes needed by EF. A DTO for a product would look something like this.
public class Categorie
{
public int Id { get; set; }
public string Naam { get; set; }
public string Omschrijving { get; set; }
public byte[] Plaatje { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Naam { get; set; }
public string Omschrijving { get; set; }
public double Prijs { get; set; }
public List<Categorie> categorien = new List<Categorie>();
public List<Categorie> Categorien
{
get { return categorien; }
set { categorien = value; }
}
public byte[] Plaatje { get; set; }
}
Notice that the DTO for Categorie does not contain a list of products, since in this case you want a listing of products. If you keep the field names the same for your DTO's as your entities AutoMapper will handle the mapping automatically. I usually keep the same class name for the DTO's and just distinguish them from the entities by having a different namespace. Your REST API method would look something like this.
// GET api/Rest/5
public Product GetProduct(int id)
{
Product product = db.Producten.Find(id);
return Mapper.Map<Product, Dto.Product>(product);
}