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

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.

Related

Table Splitting - Migration Warning

My scenario:
I have a Product that has various properties such a price, size, etc. that are declared in the Product Entity.
Additionally, a Product can have a collection of StockRequirements, i.e. when that Product is used the constituent StockItems can be depleted by the StockRequirement quantity accordingly.
Under one use case I just want the Product so that I can play with the core properties. For another use case I want the Product with its StockRequirements.
This means that when retrieving a Product I may be using it in different contexts. My chosen approach has been to use EF table splitting.
I have one repository for Products and one repository for ProductStockRequirements. They are referring to the same unique Product.
The Product repository will provide a Product Entity with the core details only.
The ProductStockRequirements repository will provide ProductStockRequirements entity which does not have the core details, but does have the list of StockRequirements.
This seemed a reasonable approach so that I am not retrieving 'owned' StockRequirements when I only want to change the price of the product. Similarly, if I'm only interested in playing with the StockRequirements then I don't retrieve the other core details.
Entities
class Product
{
public int Id { get; set; }
public string CoreProperty { get; set; }
}
class ProductStockRequirements
{
public int Id { get; set; }
public List<StockRequirement> StockRequirements { get; set; }
}
Product Mapping
b.ToTable("Products");
b.HasKey(p => p.Id);
b.Property(p => p.CoreProperty).IsRequired();
ProductStockRequirementsMapping
b.ToTable("Products");
b.HasKey(p => p.Id);
b.OwnsMany<StockRequirement>(p => StockRequirements, b =>
{
b.ToTable("StockRequirements");
b.WithOwner().HasForeignKey("ProductId");
}
b.HasOne<Product>()
.WithOne()
.HasForeignKey<ProductStockRequirements>("Id");
When running a migration, I get the warning:
The entity type 'ProductStockRequirements' is an optional dependent
using table sharing without any required non shared property that
could be used to identify whether the entity exists. If all nullable
properties contain a null value in database then an object instance
won't be created in the query. Add a required property to create
instances with null values for other properties or mark the incoming
navigation as required to always create an instance.
Focusing on the advice:
mark the incoming navigation as required to always create an instance
I have tried:
b.HasOne<Product>()
.WithOne()
.HasForeignKey<ProductStockRequirements>("Id")
.IsRequired();
and
b.HasOne<Product>()
.WithOne()
.IsRequired()
.HasForeignKey<ProductStockRequirements>("Id");
to no avail.
The warning does not appear to result in any bad behaviour. All my tests are passing. But, it seems that I should be able to create a map that removed this warning, but cannot find the way.
This should really just be
class Product
{
public int Id { get; set; }
public string CoreProperty { get; set; }
public List<StockRequirement> StockRequirements { get; set; } = new List<StockRequirement>();
}
As the StockRequiremens are not part of the Product entity, and related data isn't loaded unless you request it.
And the Entity model is simply not the correct layer to define your aggregates. An Aggregate is defined by selecting a single Entity from your entity model along with 0-few related entities. Typically you include the closely-related and weak entities together in an aggregate.
If your entity model is a graph of 23 related entities, you might organize it into 10 separate and partially-overlapping aggregates or sub-graphs.

Entity Framework add assocations to many to many by key only

Suppose you have one of the simplest text book models:
Product {
Categories
}
Where a product can be associated to 0 to many categories.
Suppose category looks similar to
Category {
int Id { get; set; }
string Name {get; set; }
}
Now I want to associate my product to (existing) categories with ids 1, 2, and 3. Is there anyway to create this association without loading categories 1, 2, and 3 into memory?
I know this would be possible with a single Category where my Product model had Category and CategoryId on it. Is there a similar convention for bonding multiple items?
To reiterate, my purpose is to avoid querying categories. I already have the identifiers. With direct sql I could easily establish these relationships by key only (in fact the association table is literally just the keys). I want to know if there's an "entity framework way" of doing this or whether direct sql is the only option.
You could create category instances with just the id and attach them to the context. Then you could add to the product, without having to pull the categories from the database.
For example:-
var category = new Category { Id = 1 };
db.Categories.Attach(category);
product.Categories.Add(category);
db.SaveChanges();

Access underlying DbContext (or run stored procedure) from Entity Framework POCO method

Is it possible to access the underlying DbContext (the DbContext that has populated this object/has this object in its cache/is tracking this object) from inside a model object, and if so, how?
The best answer I have found so far is this blog post which is five years old. Is it still the best solution available?
I’m using the latest version of Entity Framework if that matters.
Here's a sample to clarify my question:
I have a hierarchical tree. Let’s say it is categories that could have sub-categories. The model object would be something like this:
class Category
{
string CategoryId { get; set; }
string Name { get; set; }
virtual Category Parent { get; set; }
virtual ICollection<Category> Children { get; set; }
}
Now, if I want to access all descendants of a category (not just its immediate children) I can use a recursive query like this:
class Category
{
//...
IEnumerable<Category> Descendants
{
get
{
return Children.Union(Children.SelectMany(q => q.Descendants));
}
}
}
which works, but has bad performance (due to multiple database queries it needs to perform).
But suppose I have an optimized query that I can run to find descendent (maybe I store my primary key in a way that already contains path, or maybe I’m using SQL Server data type hierarchyid, etc.). How can I run such a query, which needs access to the whole table/database and not just the records available through model object’s navigational properties?
This can be either done by running a stored procedure/SQL command on the database, or a query like this:
class Category
{
//...
IEnumerable<Category> Descendants
{
get
{
// this won't work, because underlying DbContext is not available in this context!
return myDbContext.Categories.Where(q => q.CategoryId.StartsWith(this.CategoryId));
}
}
}
Is there a way at all to implement such a method?

Making a temporary ID for entities in EF before saving

If I have two tables, which have an Id, whish is an autogenerated int (seed), anyway I have a many to many relationship between these two tables which requires another table.
Now, I do a "dry run" to generate the items for the first two table before saving them, this works perfect. The problem is when I try to generate the items for the (many-many relationship) in the third table. Before saving the items all Ids in the first two tables will be set to 0, when adding items to the relation table I have no problems, the problems comes when saving the tables because the relationship table will have the Ids of 0.
Is there a way to overcome this problem? like assigning a temp value which will be automatically changed to the real Id in the relationship table before saving it ?
For the same reason, I've chosen not to use default Seed methods (AddOrUpdate) provided by EF, but I'm rather writing my own seed methods.
Now if I want to set up relationships, I'm not explicitly using ID's, but rather use navigational properties.
Imagine the scenario:
public class User
{
public int Id {get;set;}
public string Name {get;set;}
public virtual IList<Roles> Roles { get;set;}
}
public class Role
}
public int Id {get;set;}
public string Name {get;set;}
public virtual IList<User> Users {get;set;}
}
Doing this will seed both values for user and roles and relationships once you hit the save changes:
Role admin = new Role
{
Name = "Administrator"
};
Role basic = new Role
{
Name = "Basic"
};
User user = new User
{
Name = "John",
Roles = new List<Role>()
{
basic,
admin
}
}
db.Users.Add(user);
db.SaveChanges();

can't delete object that has a many-to-many relationship

these are my simplified entities:
public class User : Entity
{
public virtual ICollection<Role> Roles { get; set; }
}
public class Role : Entity
{
public virtual ICollection<User> Users { get; set; }
}
var user = dbContext.Set<User>().Find(id);
dbContext.Set<User>().Remove(user);
dbContext.SaveChanges(); // here i get error (can't delete because it's the
//referenced by join table roleUsers
the problems is that the join table references the user table and ef doesn't remove the records from the join table before deleting the user
I tried writing test cases and I noticed that:
if use the same context to add user with roles, save changes, remove and again save changes it works
but if I use 2 different contexts one for insert and another one for delete I get this error
You must first clear Roles collection (user's roles must be loaded) before you will be able to remove user.
If you want to just get this deleting just do what the error is saying.
as a part of your DELETE method, you should do this, in order.
1) Get User including its related roles you can user User.Include(r=>r.roles)
2) iterate through and delete the roles for the given user (make sure you use a toList() when you do this loop )
3) Delete the user
4) savechanges
user.Roles
.ToList()
.ForEach(role => user.Roles.remove(role));
context.Users.remove(user);
context.SaveChanges();