MVC EF: Adding one to many component by ID - entity-framework

I am working with two classes Company and Visitor that have a one-to-many relationship.
public class Company
{
public int CompanyID { get; set; }
public string CompanyName { get; set; }
public virtual ICollection<Visitor> Visitors { get; set; }
}
public class Visitor
{
public int VisitorID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int CompanyID { get; set; }
public bool SendNewsletter { get; set; }
public virtual Company Company { get; set; }
}
I have a page where a Visitor can fill out information about themselves. The idea is that if a Company is not in the db, it will get added to the list. If the CompanyName the Visitor enters matches a name in the db, the Company is associated with the Visitor, rounding out its required info, which is then added to its own db.
var companyExists = db.Companies.Any(c => c.CompanyName == visitor.Company.CompanyName);
if(companyExists)
{
var existingCompany = db.Companies.SingleOrDefault(c => c.CompanyName == visitor.Company.CompanyName);
visitor.CompanyID = existingCompany.CompanyID;
visitor.Company = existingCompany;
}
db.Visitors.Add(visitor);
db.SaveChanges();
This code works but it seems redundant. I am associating the Visitor's CompanyID with the existing Company and then doing the same for the Company. Everything I read suggests that updating the Visitor's CompanyID should be sufficient but if I don't map the existingCompany to the Visitor's Company parameter, a second CompanyID is created. I feel like I'm missing some crucial point and am hoping someone here can point me in the right direction.

You don not need set visitor.Company. You can get Company information by CompanyID and you can use Where and FirstOrDefault to avoid redundant like this:
var companyExists = db.Companies.Where(c => c.CompanyName == visitor.Company.CompanyName).FirstOrDefault();
if (companyExists)
{
visitor.CompanyID = companyExists.CompanyID;
// visitor.Company = companyExists;
}
Notice that public virtual Company Company { get; set; } allows the Entity Framework to create a proxy around the virtual property so that the property can support lazy loading and more efficient change tracking. Please see this post for more information about virtual property in EF.

Related

EF Code First relations and associative entity

So I need to have User, Companies and Channels.
User:
is tied to one Company
Channel:
is tied to one Company
can have many users
Company:
can have many users
can have many channels
public class User {
public long Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public virtual Company Company { get; set; }
public virtual ICollection<Channel> Channels { get; set; }
}
public class Company {
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<Channel> Channels { get; set; }
}
public class Channel {
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual Company Company { get; set; }
}
public class CompanyUser {
public long Id { get; set; }
public long CompanyId { get; set; }
public virtual Company Company { get; set; }
public long UserId { get; set; }
public virtual User User { get; set; }
public bool IsOwner { get; set; }
public bool ChannelLimit { get; set; }
}
public class ChannelUser {
public long Id { get; set; }
public long ChannelId { get; set; }
public virtual Channel Channel { get; set; }
public long UserId { get; set; }
public virtual User User { get; set; }
public bool IsOwner { get; set; }
}
How to make this efficient and working? What's the way to handle all relations?
Thanks
You have defined a number of many-to-many relationships where 1-to-many would suffice:
User is tied to one Company
Channel is tied to one Company, can have many users
Company can have many users, can have many channels
public class User
{
[Key]
public int UserId { get; set; }
public virtual Channel Channel { get; set; }
}
public class Channel
{
[Key]
public int ChannelId { get; set; }
public virtual Company Company { get; set; }
public virtual ICollection<User> Users { get; set; } = new List<User>();
}
public class Company
{
[Key]
public int CompanyId { get; set; }
public virtual ICollection<Channel> Channels { get; set; } = new List<Channel>();
}
When mapped, the company mapping will have a HasMany(x => x.Channels).WithRequired(x => x.Company);
The channel mapping will have a HasMany(x => x.Users).WithOptional();
To get Users for a company you can go through Channels:
var companyUsers = context.Companies
.Where(c => c.CompanyId == companyId)
.SelectMany(c => c.Channels.SelectMany(ch => ch.Users))
.ToList();
That being a very basic example to get all users associated with a single company. When reading company and related channel/user info you can dive through the association to select and filter the details as desired.
Just as with DB design, it is usually advisable to keep entity relationships relatively normalized to avoid references to each other scattered everywhere. EF does a great job of building queries to pull data through the navigation properties where needed.
Edit: Ok, users can create a channel, potentially creating multiple channels and assuming from the earlier description multiple other users can be assigned to the channel. Channels are also potentially "owned" by one company.
Take 2:
public class User
{
[Key]
public int UserId { get; internal set; }
public virtual Company Company { get; internal set; } // optional
public virtual ICollection<ChannelUser> ChannelUsers { get; internal set; } = new List<ChannelUser>();
[NotMapped]
public ICollection<Channel> Channels
{
get { return ChannelUsers.Select(x => x.Channel).ToList(); }
}
[NotMapped]
public ICollection<Channel> OwnedChannels
{
get { return ChannelUsers.Where(x => x.IsOwned).Select(x => x.Channel).ToList(); }
}
}
public class Channel
{
[Key]
public int ChannelId { get; internal set; }
public virtual ICollection<ChannelUser> ChannelUsers { get; internal set; } = new List<ChannelUser>();
public virtual ICollection<ChannelCompany> ChannelCompanies { get; internal set; } = new List<ChannelCompany>();
[NotMapped]
public User OwnerUser
{
get { return ChannelUsers.SingleOrDefault(x => x.IsOwner)?.User; }
}
[NotMapped]
public User OwnerCompany
{
get { return ChannelCompanies.SingleOrDefault(x => x.IsOwner)?.Company; }
}
}
public class Company
{
[Key]
public int CompanyId { get; internal set; }
}
public class ChannelUser
{
public int ChannelId { get; internal set; }
public int UserId { get; internal set; }
public bool IsOwner { get; internal set; }
public virtual Channel Channel { get; internal set; }
public virtual User User { get; internal set; }
}
public class ChannelCompany
{
public int ChannelId { get; internal set; }
public int CompanyId { get; internal set; }
public bool IsOwner { get; internal set; }
public virtual Channel Channel { get; internal set; }
public virtual Company Company { get; internal set; }
}
This maps similar to your original with many-to-many relationships between Channel and User and Company. The concept of an "owner" would be on the joining tables. I added some unmapped helper properties to make it easier for code using the entities to navigate through the linking entities. Be warned though that these properties cannot be used in EF Linq expressions. For instance when writing Linq queries against the dbSets and navigation properties you will need to use the linking entity collections because EF won't be able to resolve the unmapped ones. EF6 & EF Core 3+ will throw exceptions if you try to use them in a query, EF Core 2 would generate a warning and auto-materialize the query into memory which is a potentially huge performance/memory sink.
The trouble with models like this is that they can get the job done, but will rely on your code logic to enforce that the rules around expected relationships between channels, users, and companies are enforced. For instance, expecting that every channel has 1, and only 1 owning user (if required) and 0 or 1 owning company. (if optional)
To do this, I would recommend using Actions (methods) on the entities to modify state rather than accessing setters/collections directly. For example, if you can create a channel for a user, have a CreateChannel() method on user with the required parameters:
public Channel CreateChannel(string name /* , other required fields */)
{
var channel = new Channel
{
Name = name,
//...
};
var channelUser new ChannelUser { Channel = channel, User = this, IsOwner = true });
ChannelUsers.Add(channelUser);
channel.ChannelUsers.Add(channelUser);
return channel;
}
This assumes that the DbContext that read the associated User entity is in-scope and alive when user.CreateChannel() is called, and SaveChanges() is called afterwards. For instance, somewhere in a controller action to create a Channel from data provided in the Post:
using (var context = new AppContext())
{
var user = context.Users.Single(x => x.UserId == userId);
var channel = user.CreateChannel(channelName /*, .... */);
context.SaveChanges();
// Potentially return view information for the newly created Channel...
}
Changing a channel owner between users (whether limited to user already associated to the channel or a new user) can be contained in a method on Channel to update the appropriate collections in one operation to ensure the IsOwner state is kept valid. This is where I will make setters internal to discourage attempting to mutate state with setters instead of using action methods.
Anyhow, this should hopefully give you a few things to think about. Ultimately look to narrow down the allowed relationships to the minimum viable connection points between the entities. Many-to-many relationships can complicate things in terms of enforcing valid combinations.

Nested Properties with Inheritance

Online shop I am working on has entity Order that has member DeliveryDetails.
The purpose of DeliveryDetails is to contain data which is specific to delivery method selected by user (e.g. Shipping or Pick Up From Store), while some details are common for all methods (e.g. Firstname, Lastname, PhoneNumber). I was thinking about structure similar to the following using inheritance:
public class Order {
// ....other props...
public DeliveryMethodType DeliveryMethodType { get; set; }
public DeliveryDetailsBase DeliveryDetails { get; set; }
}
public class DeliveryDetailsBase
{
public int Id { get; set; }
public string CustomerId { get; set; }
public Order Order { get; set; }
public int OrderId { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string PhoneNumber { get; set; }
}
public class DeliveryDetailsShipping : DeliveryDetailsBase
{
public string Street { get; set; }
public string Building { get; set; }
public string Appartment { get; set; }
public string PostalCode { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
public class DeliveryDetailsPickupFromStore : DeliveryDetailsBase
{
public string StoreCode { get; set; }
}
However, I can't figure out how to make DeliveryDetails prop be assigned to different type of delivery method details depending on what method customer selected and how to fit it in EntityFramework on ASP.Core.
Workarounds I have already tried:
-> (1). Creating "super class" contatining props for ALL delivery methods and populate in db only those that are needed for selected delivery method (selection via setting enum DeliveryMethodType). OUTCOME: works, but with 1 big and ugly table featuring multiple nulls.
-> (2). In Order, creating prop DeliveryDetails which in turn embraces DeliveryDetailsPickupFromStoreDATA & DeliveryDetailsShippingDATA. OUTCOME: works, but with several related tables and quite a lot of ugly code checking selected type from enum, instantiating specific subclass for chosen delivery method and setting to null other unused subclasses.
TO SUM UP: Is there any more elegant and feasible way to organize this?
Is there any more elegant and feasible way to organize this?
Keep it simple, and inheritance isn't usually simple. :)
As a general rule I opt for composition over inheritance. It's easier to work with. Given an order that needs to be delivered to an address or to a store:
public class Order
{
public DeliveryMethod DeliveryMethod { get; set; } = DeliveryMethod.None;
public virtual OrderDeliveryAddress { get; set; } // should never be null.
public virtual OrderDeliveryStore { get; set; } // not null if delivery mode = store.
}
public class Address
{
public string Street { get; set; }
public string Building { get; set; }
public string Appartment { get; set; }
public string PostalCode { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
public class OrderDeliveryAddress
{
public virtual Order Order { get; set; }
public virtual Address Address { get; set; }
}
public class Store
{
public int StoreId { get; set; }
public virtual Address { get; set; }
}
public class OrderDeliveryStore
{
public virtual Order Order { get; set; }
public virtual Store Store { get; set; }
}
Where DeliveryMethod is an Enum. { None = 0, ToAddress, ToStore }
When an order is placed the operator can choose to deliver it to an address, selecting the address of the customer, or entering a new address record; or they can deliver it to a store which can also set the OrderDeliveryAddress to the address of the store. You can establish checks in the database/system to ensure that the data integrity for the delivery method and referenced OrderDeliveryAddress/OrderDeliveryStore are in sync and raise any mismatches that might appear.
One consideration would be that when it comes to deliveries, you will probably want to clone a new Address record based on the customer address, or store address as applicable at the time of ordering rather than referencing their current address record by ID. The reason would be for historical integrity. An order will have been delivered to the address at that point in time, and if a customer address or store address changes in the future, past orders should still show the address that order was delivered.
EF Core has only implemented Table Per Hierarchy (TPH) inheritance.
Table Per Type (TPT) is still an open ticket (not implemented).
Table Per Concrete Type (TPC) is also still an open ticket (not implemented).
So, if TPH meets your requirements, you can follow this guide.
Basically, one table will be used and an extra column called Discriminator will be used to determine which implementation the record corresponds to.
If you are just getting started with Entity, my recommendation would be to not use inheritance and just use nullable columns for data that may or may not be needed depending on the type.

Entity Framework one to many : SqlException

I have a little problem when I try to save an item in my DB using EntityFramework.
My classes looks like:
public partial class Site
{
public int Id { get; set; }
public string Name { get; set; }
public string LongName { get; set; }
public string Adress { get; set; }
public City City { get; set; }
public Country Country { get; set; }
public string VATNumber { get; set; }
}
public class Country
{
public int CountryId { get; set; }
public string Name { get; set; }
public string IsoCode { get; set; }
}
And when I try to create a new site in my controller it works, but when I try to add a link to an existing Country :
if (SiteProvider.GetSiteByName(Site.Name) == null)
{
Site.Country = CountryProvider.GetCountryById(1);//it's working, i see the link to the right country
SiteProvider.Create(Site);
}
public static void Create(Site Site)
{
using (MyDBContext Context = new MyDBContext())
{
Context.Site.Add(Site);
Context.SaveChanges(); //here is the problem
}
}
I got this error:
SqlException: Cannot insert explicit value for identity column in
table 'Country' when IDENTITY_INSERT is set to OFF
Thanks in advance for your help.
Add CountryId property to Site class and when adding a new Site set CountryId instead of Country property
public int CountryId { get; set; }
[ForeignKey("CountryId")]
public Country Country{ get; set; }
You have a slight issue with your use of contexts here. You have used one DBContext instance to load the country (where this country object will be tracked) and then a second DBContext to save the site (where the first country object is a property).
It is preferable to perform all your operations for a single unit of work by using one DB context (that would be shared between your classes) and the responsibility for disposing of it to be handled outside your repository layer.

Foreign key with OR logic

I have CodeFirst design like this:
public class Email
{
public string Address { get; set; }
public Company Company { get; set; }
public int? CompanyId { get; set; }
public User User { get; set; }
public int? UserId { get; set; }
//many other props
}
public class Company
{
public List<Email> Emails { get; set; }
}
public class User
{
public List<Email> Emails { get; set; }
}
In a good way, Email can belong to only one foreign key: CompanyId or UserId. But now it allows CompanyId and UserId. It's wrong. Anyway, that design with nullables is ugly. For example, to get all emails linked to companies I need do this:
var companyEmails = _context.Emails.Where(x => x.CompanyId.HasValue);
I feel there is a better approach to define multiply foreign keys with OR logic. Please, help me find a way.
If you want to have the only one reference to Company or User, that mean it will not be FK and also you should have additional field with description, to which table this field points.
Alternatively, you can try Table per Hierarchy approach. At this case database table will remain almost the same, only new Discriminator column will be implicitly added, to distinguish classes, but you will can to write more "elegant" code:
public abstract class Email
{
public string Address { get; set; }
}
public class CompanyEmail : Email
{
public Company Company { get; set; }
public int? CompanyId { get; set; }
}
public class UserEmail : BaseEmail
{
public User User { get; set; }
public int? UserId { get; set; }
}
Usage:
var companyEmails = _context.Emails.OfType<CompanyEmail>();
//underlying query:
//select * from dbo.Emails where Discriminator = 'CompanyEmail'

Entity Framework 4 CTP 5 POCO - Many-to-many configuration, insertion, and update?

I really need someone to help me to fully understand how to do many-to-many relationship with Entity Framework 4 CTP 5, POCO. I need to understand 3 concepts:
How to config my model to indicates
some tables are many-to-many.
How to properly do insert.
How to properly do update.
Here are my current models:
public class MusicSheet
{
[Key]
public int ID { get; set; }
public string Title { get; set; }
public string Key { get; set; }
public virtual ICollection<Author> Authors { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Author
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public string Bio { get; set; }
public virtual ICollection<MusicSheet> MusicSheets { get; set; }
}
public class Tag
{
[Key]
public int ID { get; set; }
public string TagName { get; set; }
public virtual ICollection<MusicSheet> MusicSheets { get; set; }
}
As you can see, the MusicSheet can have many Authors or Tags, and an Author or Tag can have multiple MusicSheets.
Again, my questions are:
What to do on the
EntityTypeConfiguration to set the
relationship between them as well as
mapping to an table/object that
associates with the many-to-many
relationship.
How to insert a new music sheets
(where it might have multiple
authors or multiple tags).
How to update a music sheet. For
example, I might set TagA,
TagB to MusicSheet1, but later I need to change the tags to TagA
and TagC. It seems like I need
to first check to see if the tags
already exists, if not, insert the
new tag and then associate it with
the music sheet (so that I doesn't
re-insert TagA?). Or this is
something already handled by the
framework?
Thank you very much. I really hope to fully understand it rather than just doing it without fully understand what's going on. Especially on #3.
In the EF4 CTP5 the relationship is done by default convention when you put public virtual ICollection in each of the classes of the many to many relationship, as you already have done, your context class should look like this:
public class YourContextName : DbContext
{
public DbSet<MusicSheet> MusicSheets { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Author> Authors { get; set; }
}
Very simple you just create a instance of the MusicSheet class and then add all the instances of you authors and tags to each of the collections of Authors and Tags in your MusicSheet, and then add your instance of MusicSheet to your context collection of MusicSheets and then call SaveChanges:
MusicSheet musicSheet = new MusicSheet
{
Title = "Music Sheet 1",
Key = "Key",
Authors = new List<Author>
{
new Author
{
Name = "Author 1",
Bio = "Author 1 biographic text..."
},
new Author
{
Name = "Author 2",
Bio = "Author 2 biographic text..."
}
},
Tags = new List<Tag>
{
new Tag {TagName = "TagA"},
new Tag {TagName = "TagC"}
}
};
var context = new YourContextName();
context.MusicSheets.Add(musicSheet);
context.SaveChanges();
To update you have to load your MusicSheet and remove the tags you don't want and then add the ones you need to add, this is how:
var context = new YourContextName();
var myMusicSheet = context.MusicSheets.First();
//The Tag you wnat to remove.
var tagToRemove = myMusicSheet.Tags.First();
var tagToAdd = new Tag {TagName = "TagX"};
myMusicSheet.Tags.Remove(tagToRemove);
myMusicSheet.Tags.Add(tagToAdd);
context.Entry(myMusicSheet).State = EntityState.Modified;
context.SaveChanges();
You can also find any author and/or tag that you know that exist and added to your MusicSheet and vice versa, but this is the foundation.
Remember this is for the EF4 CTP5 Code first...
Excuse me my English is not my main language, I hope this can help you, best regards from Dominican Republic.
PS: Don't forget to add references to EntityFramework and System.Data.Entity, is your responsibility to do anything else like unit test, validation, exception handling...etc
EDIT:
First you need to add a constructor to your models:
public class Tag
{
[Key]
public int ID { get; set; }
public string TagName { get; set; }
public Tag()
{
MusicSheets = new List<MusicSheet>();
}
public virtual ICollection<MusicSheet> MusicSheets { get; set; }
}
...Then you can do something like this:
var context = new YourContextName();
var newMusicSheet = new MusicSheet();
newMusicSheet.Title = "Newly added Music Sheet";
//Your existing Tag.
var existingTag = contex.Tags.Find(3);
existingTag.MusicSheets.Add(existingTag);
context.Entry(existingTag).State = EntityState.Modified;
context.SaveChanges();
You can do the same for all your models.
I hope this can help you!
You do not really need an EntityTypeConfiguration to set the relationship between them. It should work as it is right now. With CTP5 all you have to do to establish a many-to-many relationship is to include ICollection in both entities.
Now about how to perform inserts and deletes, there are two ways I know of. The one I usually use is create an entity for the resultant table of the many-to-many relationship, then create an instance of this entity and feed it with all the data that is required, including instances of the other entities (the ones that have the many-to-many relationship). And finally I simply add it to the repository and commit the transaction (usually using a UnitOfWork class).
Quick example:
public class Item
{
public int ID { get; set; }
public string Title { get; set; }
public virtual ICollection<Bid> Bids { get; set; }
}
public class User
{
public int ID { get; set; }
public string Username{ get; set; }
public string Email { get; set; }
public string Password { get; set; }
public virtual ICollection<Bid> Bids { get; set; }
}
public class Bid
{
public int ID { get; set; }
public float Amount { get; set; }
public DateTime Date { get; set; }
public virtual Item Item { get; set; }
public virtual User User { get; set; }
}
Then I would simply create instances of the Bid entity.
public void PlaceBid(User user, Item item, int amount)
{
if (ValidateBid(amount, user, item))
{
Bid bid = new Bid
{
Amount = amount,
Date = DateTime.Now,
User = user,
Item = item
};
try
{
repository.Add(bid);
unitOfWork.Commit();
}
catch (Exception ex)
{
//TODO: Log the exception
throw;
}
}
}