I'm wondering what's the best way to handle default values for relationships when making models. (Specifically EF4)
For example, my Organization has a default Contact and I was wondering which one was the best approach. I got these two options (or any other anyone suggests if better)
Using Relationship:
public class Contact
{
public int Id { get; set; }
public string FirstName { get; set; }
}
public class Organization
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Contact> Contacts { get; set; }
//Use a relationship for the default contact?
public Contact DefaultContact { get; set; }
}
Using Value:
public class Contact
{
public int Id { get; set; }
public string FirstName { get; set; }
//Use value?
public boolean IsDefault { get; set; }
}
public class Organization
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Contact> Contacts { get; set; }
}
I'd go with Option 1. While 2 is definitely easier to implement, it doesn't enforce rules such as "There cannot be 2 default contacts". I end up with something like the following:
public class Organization {
// ...
public virtual ICollection<Contact> { get;set; }
[ForeignKey("DefaultContactId")]
public Contact DefaultContact { get;set; }
public int? DefaultContactId { get;set; }
}
There's a limitation of this approach - it doesn't work nested deletes (see this question for more details). Because of this, you need to disable CascadeOnDelete for the 1-to-many relationship:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>().HasRequired(co => co.Organization).WithMany().WillCascadeOnDelete(false);
}
(Code done without testing, but should work)
The other problem with this is that it's not possible to add the Default Contact at the same time as you're adding the organization, as EF can't figure out the correct order of statements. You need to call .SaveChanges between each. You can still use a TransactionScope to overcome this, but it's not clean:
using (var ts = new TransactionScope())
{
Organization org = new Organization
{
// ...
Contacts = new Collection<Contact>()
}
org.Contacts = new Contact() {};
orgRepo.SaveChanges();
// Now wire up the default contact
org.DefaultContact = org.Contacts.First();
orgRepo.SaveChanges();
}
Related
I have contexts with entities like this:
public class CompanyContext : DbContext
{
public DbSet<StoreModel> Stores { get; set; }
// Other entities
}
public class DepartmentContext : DbContext
{
public DbSet<OrderModel> Orders { get; set; }
// Other entities
}
public class StoreModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<OrderModel> ReceivedOrders { get; set; }
public virtual ICollection<OrderModel> PreparedOrders { get; set; }
public virtual ICollection<OrderModel> IssuedOrders { get; set; }
}
public class OrderModel
{
public Guid Id { get; set; }
public string Details { get; set; }
public StoreModel GettingStore { get; set; }
public StoreModel PreparingStore { get; set; }
public StoreModel IssuanceStore { get; set; }
}
For example a user makes an order in storeA, but wants to receive it in storeC, and it order will preparing in storeB. And I needs a statiscics about store received/prepared/issued orders.
When I try to create a migrations, EF throws exceptions "Unable to determine the relationship represented by navigation 'OrderModel.GettingStore' of type 'StoreModel'" and "Unable to determine the relationship represented by navigation 'StoreModel.IssuedOrders' of type 'ICollection<OrderModel>'". If I understand correctly, this happens because entities are defined in different contexts.
Now I just use next model:
public class OrderModel
{
public Guid Id { get; set; }
public string Details { get; set; }
public Guid GettingStoreId { get; set; }
public Guid PreparingStoreId { get; set; }
public Guid IssuanceStoreId { get; set; }
}
This works fine, but perhaps there are options that allow to create such a structure using navigation properties, with correct relationships between these entities from different contexts(databases).
First, the map of a different database was not placed in tables of different application formats, so think that you have a domain that should be well defined in your application, that way you would have the mapping of your application like this:
public class DomainNameContext: DbContext
{
public DomainNameContext(): base()
{
}
public DbSet<StoreModel> Stores { get; set; }
public DbSet<OrderModel> Orders { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// config mapping methods
}
}
another thing, the relation you are using doesn't work so you can't have a repetition of Orders within the same class because this is not one -> many, this statement means that a StoreModel line can have many lines in the OrderModel this way would be like this
public class OrderModel
{
public Guid Id { get; set; }
public string Details { get; set; }
public Guid StoreModeId { get; set; } // this part will show the entity framework that this is the fk it will correlate
public StoreModel StoreModel { get; set; }
}
public class StoreModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<OrderModel> OrderModels { get; set; }
}
see that if you are wanting to have many StoreModel related to many OrderModel then you need to use many -> many which microsoft documentation foresees to use as well
good to map this within its context it is necessary in OnModelCreating to use its mapping like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// config mapping methods
modelBuilder.Entity<StoreModel>()
.HasMany<OrderModel>(g => g.OrderModels )
.HasForeignkey<Guid>(s => s.StoreModeId )
}
you can have a look at the microsoft documentation enter link description here, enter link description here
now if you need to map between contexts you will have to use dapper to make separate queries in separate bases the entity has support for that in this link enter link description here
and then you can make the necessary inner joins so that you can use it but natively this does not exist, I advise you to rethink your database so that it can make more sense to a relational model, perhaps putting types for your StoreModel and OrderModel so you can use the way I wanted the types GettingStore, PreparingStore, IssuanceStore using an enum for this to make it explicit
So I have the following entities defined.
internal class DeliveryArea
{
public string Postcode { get; set; }
public string State { get; set; }
public string Country { get; set; }
public ICollection<DeliveryPrice> HasDeliveryPrices { get; set; }
}
internal class DeliveryPrice
{
public uint Id { get; set; }
public DeliveryArea ForDeliveryArea { get; set; }
public string DeliveryAreaPostcode { get; set; }
public string DeliveryAreaState { get; set; }
public string DeliveryAreaCountry { get; set; }
}
and my DbContext is as follow
internal class MyDbContext : DbContext
{
// DbSets.
public DbSet<DeliveryArea> DeliveryAreas { get; set; }
public DbSet<DeliveryPrice> DeliveryPrices { get; set; }
// Overrides.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(#"Data Source=Test.EFCore.db;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region DeliveryArea.
{
var entity = modelBuilder.Entity<DeliveryArea>();
// Setup case-insensitive columns.
entity.Property(i => i.Postcode).HasColumnType("TEXT COLLATE NOCASE");
entity.Property(i => i.State).HasColumnType("TEXT COLLATE NOCASE");
entity.Property(i => i.Country).HasColumnType("TEXT COLLATE NOCASE");
// Setup composite PK.
entity.HasKey(nameof(DeliveryArea.Postcode), nameof(DeliveryArea.State), nameof(DeliveryArea.Country));
}
#endregion
#region DeliveryPrice.
{
var entity = modelBuilder.Entity<DeliveryPrice>();
// DeliveryPrice x DeliveryArea | many-to-one
entity.HasOne(left => left.ForDeliveryArea)
.WithMany(right => right.HasDeliveryPrices)
.HasForeignKey(left => new { left.DeliveryAreaPostcode, left.DeliveryAreaState, left.DeliveryAreaCountry });
}
#endregion
}
}
When the database is generated, EF Core manage to generate appropriate FK that connects both table using the composite key. Everything looks fine and the diagram looks great.
Now, I added the following entity
internal class Currency
{
public uint Id { get; set; }
public ICollection<DeliveryPrice> ForDeliveryPrices { get; set; }
}
and updated DeliveryPrice class as follow
internal class DeliveryPrice
{
public uint Id { get; set; }
// Add the following
public Currency HasCurrency { get; set; }
public uint HasCurrencyId { get; set; }
public DeliveryArea ForDeliveryArea { get; set; }
public string DeliveryAreaPostcode { get; set; }
public string DeliveryAreaState { get; set; }
public string DeliveryAreaCountry { get; set; }
}
My DbContext is updated on top of existing, as follow
internal class MyDbContext : DbContext
{
// DbSets.
public DbSet<Currency> Currencies { get; set; }
// Existing codes remain...
// Overrides.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region Currency.
{
var entity = modelBuilder.Entity<Currency>();
// Currency x DeliveryPrice | one-to-many
entity.HasMany(left => left.ForDeliveryPrices)
.WithOne(right => right.HasCurrency)
.HasForeignKey(right => right.HasCurrencyId);
}
#endregion
// Existing codes remain...
}
}
When the new database is generated, the FK that connects both DeliveryArea and DeliveryPrice table is kinda splitted into 2, as follow
The funny thing is that when the Currencies table is renamed to, say Foo, the FK that connects both DeliveryArea and DeliveryPrice table looks OK.
UPDATE 01:
Normal looking FK
Here's a screenshot of the generated FK that splitted into 2
UPDATE 02:
Upon looking further into the issue, I've found that this is specific to DBeaver only. Viewing the same database file with other database viewer (e.g. DbSchema) does not have the issue.
Any idea what's going on?
I'm quite new to .net and entity framework (this is my first project) and I'm getting the following error when trying to update the database:
*Introducing FOREIGN KEY constraint 'FK_Rating_User_UserId' on table 'Rating' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.*
I tried doing what it says (at least I think so) by adding the following to my dbContext class:
protected override void OnModelCreating(ModelBuilder modelbuilder)
{
modelbuilder.Entity<Rating>().HasOne(u => u.User).WithMany().OnDelete(DeleteBehavior.Restrict);
modelbuilder.Entity<Rating>().HasOne(g => g.Game).WithMany().OnDelete(DeleteBehavior.Restrict);
}
Not sure have I formulated that method correctly but it did not help (I tried with different DeleteBehavior like SetNull and NoAction)
The thing that really got me confused is that the issue appears even after removing all fields related to other tables from Rating class or even all references between all classes.
My Rating class:
public class Rating
{
public long RatingId { get; set; }
//[Rating]
public virtual Game Game { get; set; } // issue appears even after removing this and User line
//[Rating]
public int Score { get; set; }
public string CommentTitle { get; set; }
public string CommentDescription { get; set; }
//[Rating]
public virtual User User { get; set; }// issue appears even after removing this and Game line
}
User class:
public class User
{
public long UserId { get; set; }
//[Required]
public bool IsModerator { get; set; }
//[Required]
public string Username { get; set; }
//[Required]
public string Email { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
//[Required]
public string Password { get; set; }
//[Required]
public string Salt { get; set; }
public string Description { get; set; }
}
Game class:
public class Game
{
public long GameId { get; set; }
//[Required]
public virtual User User { get; set; }
//[Required]
public string Title { get; set; }
public string Description { get; set; }
//[Required]
public string PricingType { get; set; }
public float MinDonation { get; set; }
public float MaxDonation { get; set; }
//[Required]
public string FileLocation { get; set; }
public float AverageRaiting { get; set; }
public int DownloadCount { get; set; }
}
GameImage class (probably unrelated to the issue just wanted to give a full context)
public class GameImage
{
public long GameImageId { get; set; }
//[Required]
public virtual Game Game { get; set; }
//[Required]
public string Location { get; set; }
//[Required]
public bool IsThumbnail { get; set; }
}
dbContext class:
public class dbContext : DbContext
{
public dbContext(DbContextOptions<dbContext> options) : base(options)
{
}
public DbSet<User> User { get; set; }
public DbSet<Rating> Rating { get; set; }
public DbSet<GameImage> GameImage { get; set; }
public DbSet<Game> Game { get; set; }
}
The issue only appeared after I tried to update the database. The first few migrations and updates were ok, however, then I tried adding [Required] annotation (you can see them commented in the above code) as I noticed that most of the fields were created as nullable in my database - after that the issue starting to occur even after removing the annotations.
In case that matters, I'm using Visual Studio 2019 and SQL Server Express
Does anyone have any idea what may be the cause of this?
Edit:
Image of of my database schema diagram from SSMS
As you can see in the database schema it's visible that there are indeed cycles in the database, however, I cannot get rid of them as Entity Framework's command "Update-Database" does not update the DB and just throws the error mentioned above.
Based on my test, you can try the following steps to solve the problem.
First, please change your dbcontext class into the following code.
public class dbContext : DbContext
{
public dbContext() : base("name=MyContext") { }
public DbSet<User> User { get; set; }
public DbSet<Rating> Rating { get; set; }
public DbSet<GameImage> GameImage { get; set; }
public DbSet<Game> Game { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
Second, please delete all the tables the database.
Third, please try the following command in your package console.
PM> Update-Database -Force
Finally, you can see the new tables in the databse.
I'm having some trouble with ASP.NET Web API with many-many relationships between models. Here are my models (which I've simplified for brevity):
public class Model1
{
public int Model1ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Model2> Model2s{ get; set; }
public string Self
{
get
{
return string.Format(CultureInfo.CurrentCulture,
"api/model1/{0}", this.Model1ID);
}
set { }
}
}
public class Model2
{
public int Model2ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Model1> Model1s{ get; set; }
public string Self
{
get
{
return string.Format(CultureInfo.CurrentCulture,
"api/model2/{0}", this.Model2ID);
}
set { }
}
}
and my relevant Model1 API controller excerpt:
public class Model1sController : ApiController
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: api/Model1s
public IQueryable<Model1> GetModel1s()
{
return db.Model1s;
}
...
}
When I navigate to /api/model1s I get a long JSON nested error, here is the innermost Exception message.
There is already an open DataReader associated with this Command which must be closed first.
What I'm trying to achieve is output like this, but I cannot figure out how to get it working.
[{
"Model1ID": 1,
"Name": "Some model 2 name",
"Model2s": [{
"Model2ID": 1,
"Name": "Another model 2 name"
}, {
"Model2ID": 2,
"Name": "Some model 2 name"
}]
}, {
"Model1ID": 2,
"Name": "Another model 1 name",
"Model2s": [{
"Model2ID": 2,
"Name": "Some model 2 name"
}]
}]
What you need is called and associative entity, some devs call them a lookup table. An associative entity will hold the “association” between two other entities. In your case I believe that there is a scaffolding engine that will build the database tables for you based on the classes you create. Someone else may be able to speak to how the scaffolding engine works.
I would create a class called “TvProgramming” and give it properties Name, Id, Host_Id, Host_Name, and List. Now with this set up you can have as many hosts and as many tv shows as you want and still create unique programming schedules.
Adjust the tv show and host objects so that they only have properties that are unique to themselves ie a TvShow will have an name, id, and maybe a length. A host may have name, id, network, and location info however notice that the host object and tv show object have no knowledge of the other, only the associative entity holds knowledge of the relationship between them.
At the end of the day what your api should return is a set of TvProgramming objects that contain the hosts and for each host a list of tv shows… here is an quick example of the class structure I’m talking about, you’ll have to tweak it a bit to fit your needs but it should get started.
namespace YourProjectName.Models
{
public class TvShow
{
public int id { get; set; }
public string name { get; set; }
public TimeSpan length { get; set; }
public string rating { }
public TvShow() { }
}
public class Host
{
public int id { get; set; }
public string name { get; set; }
public string network { get; set; }
public string city { get; set; }
public string state { get; set; }
public string zip { get; set; }
public string country { get; set; }
public Host() { }
}
public class TvProgramming
{
public int id { get; set; }
public string name { get; set; }
public int host_Id { get; set; }
public string host_Name { get; set; }
public List<TvShow> shows { get; set; }
public TvProgramming()
{
this.shows = new List<TvShow>();
}
}
}
As a way of possibly preventing the error you are getting, try modifying your Controller code like this:
public class Model1sController : ApiController
{
// GET: api/Model1s
public IQueryable<Model1> GetModel1s()
{
using (var db = new ApplicationDbContext())
{
return db.Model1s;
}
}
}
I have this model
namespace ProjectTimer.Models
{
public class TimerContext : DbContext
{
public TimerContext()
: base("DefaultConnection")
{
}
public DbSet<Project> Projects { get; set; }
public DbSet<ProjectTimeSpan> TimeSpans { get; set; }
}
public class DomainBase
{
[Key]
public int Id { get; set; }
}
public class Project : DomainBase
{
public UserProfile User { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IList<ProjectTimeSpan> TimeSpans { get; set; }
}
[ComplexType]
public class ProjectTimeSpan
{
public DateTime TimeStart { get; set; }
public DateTime TimeEnd { get; set; }
public bool Active { get; set; }
}
}
When I try to use this action I get the exception The type 'ProjectTimer.Models.ProjectTimeSpan' has already been configured as an entity type. It cannot be reconfigured as a complex type.
public ActionResult Index()
{
using (var db = new TimerContext())
{
return View(db.Projects.ToList);
}
}
The view is using the model #model IList<ProjectTimer.Models.Project>
Can any one shine some light as to why this would be happening?
Your IList<ProjectTimeSpan> property is not supported by EF. A complex type must always be part of another entity type, you cannot use a complex type by itself. If you absolutely need to have ProjectTimeSpan as a complex type, you will need to create a dummy entity type that only contains a key and a ProjectTimeSpan, and change the type of Project.TimeSpans to a list of that new type.