Foreign key compatibility with entityframework 3 - ef-core-3.1

I am working on an ASP.Net Core MVC application.
I have changed netcoreapp2.0 to netcoreapp3.1.
Everything works except the database. I have a runtime error: A query runned on sql-server does not keep field names in fk.
Here is my model:
Table("customers")]
public class Customers
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long customer_id {get;set;}
...
[ForeignKey("group_id")]
public long? group_id {get;set;}
public virtual CustomerGroup group { get; set; }
...
When i try to get Customers in my app, sql-server runs this kind of query:
SELECT * FROM customers
JOIN groups ON customers.group_id = groups.group_id
This was working great on netcoreapp2.0.
But here is what is runned now, on netcoreapp3.0 :
SELECT * FROM customers
JOIN groups ON customers.customersgroup_id = groups.group_id
As you can see, EF adds a prefix on the FK field.
What should i do in order to keep compatibility between my previous version ?
Thanks

Related

How can I use OrderBy with an aggregate of a object property with Ardalis Specification?

I am trying to query my postgresql database using Ef core and Ardalis Specification.
For the query I build I want to sort the results by using OrderBy with an aggregate of a property that is on a nested object.
The sorting I want is to sort the list of Clinics by the Clinic that has the most Reviews with high Grades. The grades are on a scale of 1-5.
So if a clinic has two reviews with Grade=5 it should come on top of a clinic that has 5 reviews with Grade=2 or Grade=4. To do this I have to calculate the mean value and then order by the highest
public class Clinic
{
public int Id { get; set; }
public ICollection<Review> Reviews {get; set;}
}
public class Review
{
public int Id { get; set; }
public decimal Grade {get; set;}
}
My query so far, which doesnt work as intended as it only gets the highest value. Can I insert a mean-value calculation here somehow?
public ClinicFilterPaginatedSpecification()
{
Query.OrderByDescending(x => x.Reviews.Max(x => x.Grade ));
}
Running the query:
var filterSpec = new ClinicFilterSpecification();
var itemsOnPage= await _clinicRepo.ListAsync(filterSpec);
As Ivan Stoev notes in his comment, you should be able to use the .Average() command:
public ClinicFilterPaginatedSpecification()
{
Query.OrderByDescending(clinic => clinic.Reviews.Average(review => review.Grade ));
}
Have you tried this and if so is it working or producing an error?
I should mention that this is not directly related to the Specification package, in the sense that the expression is not altered in any way. Whatever works on EF, should work through specs as well. We're passing the expression as it is.
Now the question is how EF would behave in this case when you need to aggregate some data from the collections. I think this is optimized in EF Core 5, and Ardalis' suggestion should work. Prior to EF Core 3, this scenario would have involved an explicit Join operation (not quite sure).

EF Core Cascading Referential Integrity with DeleteBehavior.Restrict does not work well

I have one sql server database created with code first. There are two tables that have a one to many relationship. The database works and is created well.
In sql server if I try to delete one of the classification records, I get an error (referencial integrity restriction). This is how I want it to work. But in ef core, if I delete one classification dbset.Remove(classification), the classification is deleted and the classification in the customer is set to null.
I think this is how it should work for DeleteBehavior.ClientSetNull.
There is a note "Changes in EF Core 2.0" in https://learn.microsoft.com/en-us/ef/core/saving/cascade-delete that explains the DeleteBehavior function.
I have the next records:
Classification:
Id Name
1 General
2 Others
Customers:
Id Name IdClassification
1 Customer A 1
2 Customer B 2
3 Customer C <null>
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
...
public int? IdClassification { get; set; }
public Classification Classification { get; set; }
}
public class Classification
{
public int Id { get; set; }
public string Name { get; set; }
...
public ICollection<Customer> Customers { get; set; }
}
public class Context : DbContext
{
public virtual DbSet<Classification> Classifications { get; set; }
public virtual DbSet<Customer> Customers { get; set; }
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Classification>(
entity =>
{
entity.HasKey(e => e.Id);
});
modelBuilder.Entity<Customer>(
entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.IdClassification);
...
// Claves foráneas
entity.HasOne(c => c.Classification)
.WithMany(x => x.Customers)
.HasForeignKey(x => x.IdClassification)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("FK_Customer_Classification");
});
}
}
Is there a way to prevent deletion of classification records in ef core? (I don't want to check if there is any customer record that is linked to the classification because I have to use the classification with more tables).
Thanks in advance.
EF Core 3.0 added several new values to the DeleteBehavior enum - ClientCascade, NoAction, ClientNoAction. Unfortunately the documentation is not updated (except for enum values in API reference), and only the ClientNoAction is mentioned in the 3.0 Breaking Changes - DeleteBehavior.Restrict has cleaner semantics:
Old behavior
Before 3.0, DeleteBehavior.Restrict created foreign keys in the database with Restrict semantics, but also changed internal fixup in a non-obvious way.
New behavior
Starting with 3.0, DeleteBehavior.Restrict ensures that foreign keys are created with Restrict semantics--that is, no cascades; throw on constraint violation--without also impacting EF internal fixup.
Why
This change was made to improve the experience for using DeleteBehavior in an intuitive manner, without unexpected side-effects.
Mitigations
The previous behavior can be restored by using DeleteBehavior.ClientNoAction.
More info is contained in the associated tracking issue - 12661: Update DeleteBehavior to be more consistent and understandable
Honestly even after reading all that, I don't find it cleaner, but even more confusing. Restrict seems to be obsoleted and replaced with NoAction, which regardless of what have been said actually does set loaded related entities navigation property/FK to null, thus causing SET NULL database behavior as you already experienced.
After trying all of them, the only option which does what you expect is the aforementioned ClientNoAction:
Note: it is unusual to use this value. Consider using ClientSetNull instead to match the behavior of EF6 with cascading deletes disabled.
For entities being tracked by the DbContext, the values of foreign key properties in dependent entities are not changed when the related principal entity is deleted. This can result in an inconsistent graph of entities where the values of foreign key properties do not match the relationships in the graph.
If the database has been created from the model using Entity Framework Migrations or the EnsureCreated() method, then the behavior in the database is to generate an error if a foreign key constraint is violated.
regardless of their note at the beginning.
With all that being said, simply replace Restrict with ClientNoAction and the issue will be solve. No database migration is needed because this change affects only the client behavior.
Well, the classification entity needs correct initialization, suppose to delete restriction rule.
modelBuilder.Entity<Classification>()
.HasKey(e => e.Id)
.HasMany(e => e.Customers)
.WithOne(e => e.Classification)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(true);
Hope this helps.

Creating a new entity with reference to an entity from another context

I have 2 projetcs PRJ1 and PRJ2 which uses their own databases DB1 and DB2. Each of these databases uses EF Code First Migration.
The PRJ1 is for managing stock of products (already exists since 4 years).
The PRJ2 is for orders (brand new project still in dev)
Now let's talk about the second project only. In my project PRJ2 I need to access data from the other database DB1. So I need to place orders for products.
Here is what I got so far for PRJ2
Note that I defined 2 different contexts.
// Context for accessing entities in DB1
public class DB1Context : DbContext
{
static DB1Context()
{
Database.SetInitializer<DB1Context>(null);
}
public DbSet<Product> Products { get; set; }
}
// Context for accessing entities in DB2
public class DB2Context : DbContext
{
static DB2Context()
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DB2Context, DAL.Migrations.Configuration>());
}
public DbSet<Anything> Anythings { get; set; }
public DbSet<Order> Orders { get; set; }
}
What works: I can query data from DB1Context (Products) or DB2Context (Anythings).
What didn't works yet: Creating my Orders entity.
// My Orders entity
public class Orders
{
public int Id { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public virtual Product Product { get; set; }
}
This entity located in DB2Context is referencing the Product entity which is part of DB1Context.
Now the problem is that as soon as I add this Entity inside my context DbSet<Order> I see that there is a migration waiting for Product. This migration is for creating Product in my DB2Context. That's not what I want. This entity already exists in DB1Context. It seems I cannot create this Order entity which is referencing Product from the other context.
Can you confirm this ? Did I missed something ? Otherwise what is the best alternative ?
I think you can't do that using Entity Framework.
That problem looks like a No-SQL database's problem. When you have multiple database like that you have to control all the CRUD for all databases. The ADO can't do it to you because you don't have data integrity.
A possible solution is you put your CRUD in your business logic layer.. or something like that.
Let suppose you have a OrderBll to control:
public class OrderBll
{
private DB1Context _DB1Context = new DB1Context();
private DB2Context _DB2Context = new DB2Context();
public List<Orders> GetOrders()
{
var orders = _DB2Context.Orders.Where(???).ToList();
var productIds = orders.Select(x => x.ProductId).Distinct().ToArray();
var products = _DB1Context.Products.Where(x => productIds.Contains(x.Id)).ToList(); // Optimize the load of all products in orders
// Set the product object in the order list
foreach( var order in orders )
{
order.Product = products.FirstOrDefault(x=>x.Id == order.ProductId);
}
return orders;
}
}
Remember you have to map the Product property in Orders like Ignore.
So, you have to set the Foreign Key by yourself and do all the constraints checks.
But, if someone else have a better solution to do that, I'll be glad to know.
Ideally, this is what you should aim for.
Put orders and products in the same database. You would then be able to create relations between orders and products. You would end up with a single EF context, and this would give you a solid solution.
If for whatever reasons you cannot put orders in the same database as products, then solutions are still available with their own limitations.
You may replicate the products table from DB1 to DB2, replication running every minute, for example. You can write your own replication component or use replication functionality of your database. If products can be deleted in DB2, replication could delete the products and orders in DB1, or just flagged deleted products. It is up to you to decide. If PRJ2 can update the products table, then replication has to be both ways. This gets more complicated. The EF context with this solution would contain relations from orders to products, and products to orders.
Another solution would be to keep in DB1 a "proxy" products table that contains the Id's of the products that are referenced in orders. Everywhere in your business logic, you decide whether you need to access the actual products table from DB1 or not. For example, when creating a new order, you would access products from DB1, and insert their Id's in the proxy table if not there. When displaying the products of an order, you would first retrieve the product Id's of the order from DB1, and then their full description from DB1. When updating the inventory of a product, as part of creating an order, you would access DB1, probably with a transaction that spans DB! and DB2.

How do I map a one to zero-or-one relationship in Entity Framework 4.2 when joining properties are of different types?

I am using Entity Framework 4.2 in a class library project. The database already exists, and I cannot modify it in any way whatsoever.
I have two model/domain classes that model two database tables. The tables both expose an Id column value, which I will refer to as ThingsId. Lets call the tables TableOfThings1 and TableOfThings2. Here are my classes:
public class TableOfThings1
{
public string ThingId { get; set; }
public virtual Thing Thing { get; set; }
}
public class TableOfThings2 //qse
{
public Int64? ThingId {get; set;}
public string ThingName { get; set; }
}
The problem is that the TableOfThings1 exposes ThingsId as a nullable varchar(64), while TableOfThings2 exposes ThingsId as a non-nullable bigint.
How can I tell the Entity Framework to join on these two keys? I have tried using this:
HasOptional(things1 => things1.thing).WithMany().HasForeignKey(t => t.ThingId);
in the EntityTypeConfiguration forTableOfThings1.
I have also tried casting in the middle of that statement, which does not work. Using the setup shown above gets me this error message currently:
"The types of all properties in the Dependent Role of a referential
constraint must be the same as the corresponding property types in the
Principal Role".
Does anyone know for sure whether/how this is possible?
This is not possible with EF. You can not even create a foreign key in the database if the column types are different. Possible workaround would be to create a view of TableOfThings1 with ThingId column type matching the TableOfThings2s column type.

EF 4.1 Code First. Table-per-type inheritance with different primary key name from its base class' primary key name

Given this:
create table Location(
LocationId int identity(1,1) not null primary key,
Address nvarchar(max) not null,
City nvarchar(max) null,
State nvarchar(max) not null,
ZipCode nvarchar(max) not null
);
create table Park(
ParkId int not null primary key references Location(LocationId),
Name nvarchar(max) not null
);
I tried this mapping:
modelBuilder.Entity<Location>();
modelBuilder.Entity<Park>().ToTable("Park");
modelBuilder.Entity<Park>().Property(x => x.LocationId).HasColumnName("ParkId");
Unfortunately that didn't work.
using (var db = new Ef())
{
var park = new Park { Name = "11th Street Park", Address = "801 11th Street", City = "Aledo", State = "TX", ZipCode = "76106" };
db.Set<Location>().Add(park);
db.SaveChanges();
}
It has this error:
The property 'LocationId' is not a declared property on type 'Park'.
Verify that the property has not been explicitly excluded from the
model by using the Ignore method or NotMappedAttribute data
annotation. Make sure that it is a valid primitive property.
How should I map Park entity so its LocationId property fall to ParkId column?
I have this mapping by the way:
public class Location
{
public virtual int LocationId { get; set; }
public virtual string Address { get; set; }
public virtual string City { get; set; }
public virtual string State { get; set; }
public virtual string ZipCode { get; set; }
}
public class Park : Location
{
public virtual string Name { get; set; }
}
If it could help, this is possible in EF 4.0 (via designer), just followed the steps in Chapter 2-11 of Entity Framework 4.0 Recipes, Problem Solution Approach. Now I'm trying it on code first via EF 4.1
[EDIT]
If I change the ParkId to LocationId, things are ok. However, with designer approach, it is possible to map the LocationId to ParkId of table Park; I want to achieve the same thing with code first
create table Park(
LocationId int not null primary key references Location(LocationId),
Name nvarchar(max) not null
);
As I know (and I tried it multiple times) code first doesn't support this => your derived type should use same column names for primary key.
This problem can be described very simply: Current fluent mapping implementation doesn't allow overriding mapping rules from parent entity => parent entity defines names of primary key columns in all derived entities.
IMO the most probable reason is that it was really designed as code first where you don't have existing database and you do not have to bother with database naming - it was up to EF to define names as it needed. Once DbContext API was released people started to use it with existing database massively. But here comes a problem: Initial use cases didn't count with this so some scenarios which are pretty easily done in EDMX are not possible. This is one of them.
Here is a workaround for this issue:
Create a view for the derived table and map your entity class that view. Rename the key column in your view so that it matches the key column in the base table.
eg:
base table User (UserID, FirstName, LastName)
derived table Manager (ManagerID, DepartmentID)
Entity Framework fails to update Manager as the key column is different!
solution:
create view UserManager
as
select
ManagerID as UserID,
DepartmentID
from Manager
Then map the Manager class to the UserManager view, instead of to the Manager table.