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();
Related
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.
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.
I use entity framework and I have many to many relationship between 2 objects.
Lets say class "question" and class "tag", when I create a new question with many tags, I have list of selected tags in viewmodel. I need to create the question so these tags are added to question but I do not want to reload all the tags.
I mean :
foreach(var id in tagUIds)
{Tag tag = gettag(id);
question.tags.add(tag); }
is not very efficient, is there a way to avoid loading the tag and adding the reference just by id of tag?
Thanks
You can create intermediate entity TagToQuestionLink, map it to many-to-many table and use Collection of this entity in question instead of collection of Tags:
public class TagToQuestionLink
{
TagId{get;set;}
QuestionId{get;set;}
}
Then you can use:
foreach(var id in tagUIds)
{
var tagLink = new TagToQuestionLink{QuestionId = question.Id, TagId = id};
question.tags.add(tagLink);
}
Do not forget, that in this solution question.tags should be ICollection<TagToQuestionLink>.
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();
I'm trying to put together a ViewModel that will have a list of users and each user will have a list of locations.
The User table and Location table are joined together through another table that holds each respective ID and some other information. This table is essentially a many to many join table.
I've tried a few different viewModel approaches and they we're severely lacking... What would be the best approach for displaying this type of information?
I assume that the issue is that you want to be able to access the collection by either User or Location. One approach could be to use ILookup<> classes. You'd start with the many-to-many collection and produce the lookups like this:
var lookupByUser = userLocations.ToLookup(ul => ul.User);
var lookupByLocation = userLocations.ToLookup(ul => ul.Location);
Update:
Per your description, it seems like you don't really need to have a full many-to-many relationship in your ViewModel. Rather, your ViewModel could have a structure like this:
public class YourViewModel
{
public IEnumerable<UserViewModel> Users { get; set; }
}
public class UserViewModel
{
// User-related stuff
public IEnumerable<LocationViewModel> Locations { get; set; }
}
If you wanted to avoid redundant LocationViewModel objects, you could pre-build a mapping between your Model and ViewModel objects:
var locationViewModels = myLocations.ToDictionary(
loc => loc, loc => CreateLocationViewModel(loc));
And then reuse these objects when populating your page's ViewModel.