How to configure an entity to save the summed value from a nested list? - entity-framework

I have two entity classes (Order, OrderItem) and one Value Object cl(Money).
Simplified code:
public class Order
{
private Order()
{
}
public int OrderId { get; private set; }
public List<OrderItem> OrderItems { get; set; } = new ();
public Money TotalGrossAmount
{
get => new Money(OrderItems.Sum(x => x.TotalNetAmount.Amount), "USD");
private set { }
}
// ...rest of code
}
public class OrderItem
{
private OrderItem()
{
}
public int OrderItemId { get; private set; }
public Money TotalNetAmount { get; private set; }
// ...rest of code
}
public class Money
{
public Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
public decimal Amount { get; private set; }
public string Currency { get; private set; }
}
For efficiency purposes, I'd like to have the summed amount of the order items in the database in the Order table.
The Order entity is configured like this:
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.ToTable(nameof(Order), DbSchemas.Default);
builder.HasKey(x => x.OrderId);
// ...rest of code
builder.OwnsOne(x => x.TotalGrossAmount, navigationBuilder =>
{
navigationBuilder.Property(p => p.Amount);
navigationBuilder.Property(p => p.Currency);
});
}
}
Migration creates the correct table structure with columns: TotalGrossAmount_Amount, TotalGrossAmount_Currency
But for this configuration, the application throws an exception:
The value of 'Order.TotalGrossAmount#Money.OrderId' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.
How should I configure Order entities correctly?

Related

Use Automapper to hide database primary key/ID (convert member with ValueConverter)

I am trying to hide the real Ids of the objects in a database using Automapper's value converters, however they are not called when projecting one to the other.
Nothing special, I would like to use Hashids to convert the int ID to a random string ID (DB->DTO) and vice versa. I want to do this for every object and every ID, but instead of my converter getting called the id's in the database simply get converted strings (so 1 would become "1" instead of for example "sd2+a!F").
My Class:
public class Category
{
public Category(string name)
{
Name = name;
}
public int Id { get; set; }
public string Name { get; set; }
public int? ParentCategoryId { get; set; }
public Category? ParentCategory { get; set; }
public ICollection<Foodstuff> Foodstuffs { get; } = new List<Foodstuff>();
public ICollection<int> FoodstuffIds { get; } = new List<int>();
public byte[] RowVersion { get; set; }
}
My DTO:
public record class Category : IGenerateETag
{
public Category(string name)
{
Name = name;
}
public string Id { get; init; }
[Required(ErrorMessage = "Category name is required.", AllowEmptyStrings = false)]
public string Name { get; init; }
public string? ParentCategoryId { get; init; }
public byte[] RowVersion { get; set; }
}
My converters:
public class HideIdConverter : IValueConverter<int, string>
{
public string Convert(int sourceMember, ResolutionContext context)
{
var hashids = new Hashids();
var shadow = hashids.Encode(sourceMember);
return shadow;
}
}
public class UnhideIdConverter : IValueConverter<string, int>
{
public int Convert(string sourceMember, ResolutionContext context)
{
var hashids = new Hashids();
var plain = hashids.Decode(sourceMember);
return plain[0]; // TODO check this;
}
}
Aaand my Automapper config:
CreateMap<Dal.Entities.Category, Category>()
.ForMember(dest => dest.Id, opt => opt.ConvertUsing(new HideIdConverter(), src => src.Id))
.ReverseMap()
.ForMember(dest => dest.Id, opt => opt.ConvertUsing(new UnhideIdConverter(), src => src.Id));
It turns out that it this is not possible, because I was using LINQ expressions, especially ProjectTo(). It is stated that:
Value converters are only used for in-memory mapping execution. They will not work for ProjectTo.
Source.
I guess I will have to map the objects after I queried them from my database.

EF Core - hierarchy using Composite Design Pattern and CTE

I want to create a catalog products. There may be catalogs or products on each node.
I decided to use the composite design pattern.
I will download the node with the children using CTE. Unfortunately there was a problem, because EF Core doesn't add parentId in the CategoryProducts table.
Additionally the class (Category as my Composite) has its own CategoryDetails class, (Product as my Leaf) has its own ProductDetails class.
How do I configure EF Core to recursively get nodes from the tree?
Is CTE a good idea?
public enum CategoryProductType
{
Category,
Product
}
public abstract class CategoryProduct
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public CategoryProductType Type { get; private set; }
protected CategoryProduct(Guid id, string name, CategoryProductType type)
{
Id = id;
Name = name;
Type = type;
}
}
public class Category : CategoryProduct
{
public string Code { get; private set; }
public CategoryDetails CategoryDetails { get; private set; }
private ICollection<CategoryProduct> _children { get; set; } = new Collection<CategoryProduct>();
public IEnumerable<CategoryProduct> Children => _children;
public Category(Guid id, string name, string code)
: base(id, name, CategoryProductType.Category)
{
Code = code;
}
}
public class CategoryDetails
{
public Guid CategoryId { get; private set; }
public Category Category { get; private set; }
public string Description { get; private set; }
private CategoryDetails() { }
public CategoryDetails(Category category, string description)
{
Category = category);
Description = description);
}
}
public class Product : CategoryProduct
{
public string Index { get; private set; }
public ProductDetails ProductDetails { get; private set; }
public Product(Guid id, string name, string index)
: base(id, name, CategoryProductType.Product)
{
SetIndex(index);
}
}
EF Core Setting:
Unfortunately I don't know anything about CTE Recursion.
However, this is an example on how I modeled a hierarchical structure (i.e. a tree) with EF Core, hopefully it can help you.
public class TreeNode
{
public int TreeNodeId { get; private set; }
public int? ParentTreeNodeId { get; set; }
public TreeNode ParentTreeNode { get; set; }
public List<TreeNode> ChildrenTreeNodes { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TreeNode>(entity =>
{
entity.HasOne(n => n.ParentTreeNode)
.WithMany(n => n.ChildrenTreeNodes)
.HasForeignKey(n => n.ParentTreeNodeId);
});
}

Why EF Core loads child entities, when I query it without .Include()

I have a simple One-To-Many relationship.
Parent:
public class Customer
{
public Customer(string name, string email)
{
Name = name;
Email = email;
}
public Customer(string name, string email, long mobile)
: this(name, email)
{
Mobile = mobile;
}
//for EF
private Customer() { }
public int Id { get; private set; }
public string Name { get; private set; }
public string Email { get; private set; }
public long? Mobile { get; private set; }
public List<Transaction> Transactions { get; private set; }
public void AddTransactions(IEnumerable<Transaction> transactions)
{
if(Transactions == null)
Transactions = new List<Transaction>();
Transactions.AddRange(transactions);
}
}
Child:
public class Transaction
{
public Transaction(DateTimeOffset date, decimal amount, Currency currency, Status status)
{
TransactionDate = date;
Amount = amount;
CurrencyCode = currency;
Status = status;
}
//for EF
private Transaction() { }
public int Id { get; private set; }
public DateTimeOffset TransactionDate { get; private set; }
public decimal Amount { get; private set; }
public Currency CurrencyCode { get; private set; }
public Status Status { get; private set; }
public int CustomerId { get; set; }
public Customer Customer { get; private set; }
}
There is a simple method, which queries one Customer and calls SingleOrDefault on it. After that it queries transactions, and when they are loaded, Customer's Transactions becomes from null to Count=5(transactions which I loaded). Why? In configuration I didn't specify .UseLazyLoadingProxies().
var customerQuery = _dbContext.Customers.AsQueryable();
if (!string.IsNullOrEmpty(request.Email))
customerQuery = customerQuery.Where(c => c.Email == request.Email);
if (request.CustomerId.HasValue)
customerQuery = customerQuery.Where(c => c.Id == request.CustomerId.Value);
var customer = await customerQuery.SingleOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
//here customer has null collection of transactions
if (customer == null)
throw new NotFoundException("Not Found.");
var transactions = await _dbContext.Transactions
.Where(t => t.CustomerId == customer.Id)
.OrderByDescending(t => t.TransactionDate)
.Take(5)
.ToListAsync(cancellationToken);
//here customer has 5 transactions.
customer.AddTransactions(transactions);
//here it has 10, because of method (following the DDD, it is used for providing business invariant)
EF configuration:
public class CustomerEntityConfiguration : IEntityTypeConfiguration<Customer>
{
public void Configure(EntityTypeBuilder<Customer> builder)
{
builder.Property(c => c.Id)
.HasMaxLength(10);
builder.Property(c => c.Email)
.HasMaxLength(25)
.IsRequired();
builder.Property(c => c.Mobile)
.HasMaxLength(10);
builder.Property(c => c.Name)
.HasMaxLength(30)
.IsRequired();
//uniqueness constraint
builder.HasIndex(c => c.Email)
.IsUnique();
builder.HasMany(t => t.Transactions)
.WithOne(t => t.Customer)
.HasForeignKey(t => t.CustomerId);
}
////////////////////////////
public class TransactionEntityConfiguration : IEntityTypeConfiguration<Transaction>
{
public void Configure(EntityTypeBuilder<Transaction> builder)
{
builder.Property(t => t.Amount)
.HasColumnType("decimal(10, 2)");
}
}
This is normal behaviour and a consequence of long-lived DbContexts. Perhaps explain why this behaviour is undesirable?
Option 1: use AsNoTracking(). This tells EF not to associate loaded instances with the DbContext. Auto-wireup will not happen.
Option 2: Use shorter-lived DbContexts. Module level DbContexts can be accessed across multiple methods. Using shorter-lived DbContexts bound in using blocks means calls wont need to worry about sharing references.

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

WCF + EF return object with FK

I am facing following issue: I have ProductOrder class which has ProductId as foreign key to Product class. When I invoke following method:
public IEnumerable<ProductOrder> GetOrders()
{
return OddzialDb.ProductOrders;
}
Orders are associated with Product so I can write something like this:
OddzialDb.ProductOrders.First().Product.Name;
but when it reaches Client it turns out that there is no association with Product which is null (only ProductId is included). In DbContext I have set
base.Configuration.ProxyCreationEnabled = false;
base.Configuration.LazyLoadingEnabled = false;
On the WCF Service side auto-generated by EF ProductOrder class looks as follows:
public partial class ProductOrder
{
public int Id { get; set; }
public Nullable<int> ProductId { get; set; }
public int Quantity { get; set; }
public virtual Product Product { get; set; }
}
What happens that it looses connections with tables associated by foreign keys?
Make your relationship virtual as in the example:
public class ProductOrder
{
public int Id { get; set; }
public virtual Product Product { get; set; }
public int ProductId { get; set; }
}
By turning your relationship virtual, the Entity Framework will generate a proxy of your ProductOrder class that will contain a reference of the Product.
To make sure it will work, Product also has to contain reference to ProductOrder:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductOrder> ProductOrders { get; set; }
}
Set these variables true on your DbContext:
Configuration.LazyLoadingEnabled = true;
Configuration.ProxyCreationEnabled = true;
On your WCF application, add the following class, which will allow for proxy serialization:
public class ApplyDataContractResolverAttribute : Attribute, IOperationBehavior
{
public ApplyDataContractResolverAttribute()
{
}
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
{
}
public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
{
DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver =
new ProxyDataContractResolver();
}
public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver =
new ProxyDataContractResolver();
}
public void Validate(OperationDescription description)
{
// Do validation.
}
}
Then on your ServiceContract interfaces you add the DataAnnotation [ApplyDataContractResolver] right among your other annotations such as [OperationContract], above any method signature that returns an entity:
[OperationContract]
[ApplyDataContractResolver]
[FaultContract(typeof(AtcWcfEntryNotFoundException))]
Case GetSingleByCaseNumber(int number);