Is it possible to build a complex relation with Entity framework? - c#

I have an application that is written on the top of ASP.NET MVC 5 framework along with Entity 6 Framework. I am using Database-first approach instead on code-first approach.
I am aware on how to build simple relation between my models using virtual navigations like public virtual RelationModel Relation { get; set } or public virtual ICollection<RelationModel> Relations { get; set }
However, the requirement is more tricky this time. I need to be able to build a relation using composite key not just a single column. I want to add a relation where the joiner/join-clause on one side should be a computed property called LocalDate and DateOf on the other side AND OwnerId column == UserId of the other side.
Here is an example of my models
My parent model looks like the following
public Entry
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Owner")]
public int OwenerId { get; set; }
public DateTime StartedAt { get; set; }
public int UtcOffset { get; set; }
public virtual User Owner { get; set; }
// This puts the StartedAt in the correct date state which is needed for the relation
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime LocalDate
{
get
{
return StartedAt.AddSeconds(UtcOffset * -1).Date;
}
}
// This is the relation that needs a complex join clause
public virtual ICollection<Bucket> Buckets { get; set }
}
Here is my child model looks like the following
public Bucket
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int UserId { get; set; }
public DateTime DateOf { get; set; } //This is a date it or stored with 00:00:00.000 time in the database
public virtual User Owner { get; set; }
}
From my Entry model, I want to be able to access my Buckets relations using the following logic Entry.LocalDate == Bucket.DateOf && Entry.UserId == Bucket.UserId
Note that the LocalDate property is NOT a database column, rather a computed property
Is it possible to construct this kind of relation between my model where I can use .Include(x => x.Buckets) to get the relations accordingly? If so, how? If it is not possible, what are other ideas that can be used to deliver the same results?

Related

Multiple Many-to-Many relationsship Entity Framework

The main goal is the ability to have a many to many relationship between the table Mucle and Exercise. I want an Exercise to have both a primary and a secodary muscle group.
Is it possible to have two icollections in one model and only one in the other?
If someone could help with the "fluent configuration" as well, I would appreciate it!
Here is the code I have got right now.
public class Muscle
{
public int MuscleID { get; set; }
public bool IsFront { get; set; }
public string Name { get; set; }
public virtual ICollection<Exercise> Exercises { get; set; }
}
public class Exercise
{
public int ExerciseID { get; set; }
// ExerciseCategory
public int ExerciseCategoryID { get; set; }
public DateTime CreationDate { get; set; }
public string Description { get; set; }
public string Name { get; set; }
public virtual ExerciseCategory ExerciseCategory { get; set; }
public virtual ICollection<Muscle> Muscles { get; set; }
public virtual ICollection<Muscle> MusclesSecondary { get; set; }
}
No way to map the model you described.
To map your model (2 n-m relationship) you would need a Junction table with a discriminator and you can't do it with EF.
You have several way to change your model to make it work with EF
You create a model (a class) for the Junction table and insert a discriminator in it. Your model changes (and I think that the new model is less clear)
Why is there a Muscles and MusclesSecondary? Can it be discriminated with an attribute of Muscle? In this case you can have the attribute in Muscle and remove Exercise.MusclesSecondary Then you have only an n-m relationship that EF handles with a Junction table.
If you want this model you can add 2 collections to Muscle (for example ExcercisesMuscle and ExercisesMusclesSecondary) and a 3rd not mapped collection where you have the content of ExcercisesMuscle and ExercisesMusclesSecondary toghether. About the ExcercisesMuscle and ExercisesMusclesSecondary they can be observable collections so you can cache the content of Exercises collection in an efficient way.

Entity Framework losing data after restarting from List

I'm having an issue with a site that I'm writing in C# ASP using Entity Framework for the database. One of the data models that I'm using to store and retrieve data called DowntimeEvent contains 2 Lists AffectedSystems and AffectedDepartments. While I'm running the application in Visual Studio those lists store and retrieve just fine. But if I stop and restart the application the DowntimeEvents are still stored in my database, however the Lists for Affected Departments, and Affected Systems are null when I try to retrieve them.
Here's the Model I'm using to store the data
public class DowntimeEventModel
{
[Key]
public int ID { get; set; }
[Required]
public DateTime StartDateTime { get; set; }
[Required]
public DateTime EndDateTime { get; set; }
public int LocationID { get; set; }
[Required]
public string Description { get; set; }
//public int DepartmentID { get; set; }
//public int SystemID { get; set; }
public virtual ICollection<int> AffectedSystems { get; set; }
public virtual ICollection<int> AffectedDepartments { get; set; }
//public virtual ICollection<SystemModel> AffectedSystems { get; set; }
//public virtual ICollection<DepartmentModel> AffectedDepartments { get; set; }
}
Here's an example Controller of how I'm saving the data, and by the way this seems to be working just fine in storing the lists.
[HttpPost]
public ActionResult DowntimeEvent(DowntimeEventModel downtimeEvent)
{
PowerteqContext.DowntimeEvents.Add(downtimeEvent);
PowerteqContext.SaveChanges();
return View(SetupDowntimeEventViewModel());
}
It was this method that tipped me off to there being an issue with data retrieval after trying to write this report and trying to figure out why AffectedSystems was sometimes null and sometimes not. In the inner foreach loop I tried to access the ListAffectedSystems directly just to see if the loop might not be null that way and it is after a restart, but it's not if I add them and don't restart.
public ActionResult ReportUptimeBySystems()
{
var EndTime = DateTime.Now;
var StartTime = DateTime.Now.AddDays(-28);
var uptimeHours = new TimeSpan(1);
if (EndTime != StartTime)
uptimeHours = EndTime - StartTime;
List<ReportUptimeBySystem> SysUps = new List<ReportUptimeBySystem>();
var DownTimes = PowerteqContext.DowntimeEvents.AsEnumerable();
var Systems = PowerteqContext.Systems.AsEnumerable();
foreach (var x in Systems)
{
ReportUptimeBySystem sys = new ReportUptimeBySystem();
sys.SystemTimeUP = uptimeHours;
sys.SystemName = x.SystemName;
foreach (var y in DownTimes)
{
if(PowerteqContext.DowntimeEvents.Find(y.ID).AffectedSystems.Contains(x.ID))
{
sys.SystemTimeUP -= y.StartDateTime - y.EndDateTime;
}
}
SysUps.Add(sys);
}
return View(SysUps);
}
Another developer suggested that the issue may be in my Entity Framework Configuration. But I don't know where to look to even try to fix that.
For reference the whole application can be found here. The database I'm using is Microsoft SQL Serverhere
Entity framework will only automatically load relationships if it finds properties representing collections of another entity. It also must be able to identify foreign keys. By standards SystemModel and DepartmentModel should have a property DowntimeEventID, otherwise you'll have to inform it how to do this for you.
You should also ensure that lazy loading isn't disabled.
https://msdn.microsoft.com/en-us/data/jj574232.aspx
Disable lazy loading by default in Entity Framework 4
Here is a good example from a related question.
Many-to-many mapping table
public class DowntimeEventModel
{
[Key]
public int ID { get; set; }
[Required]
public DateTime StartDateTime { get; set; }
[Required]
public DateTime EndDateTime { get; set; }
public int LocationID { get; set; }
[Required]
public string Description { get; set; }
public virtual ICollection<SystemModel> AffectedSystems { get; set; }
public virtual ICollection<DepartmentModel> AffectedDepartments { get; set; }
}
Assuming AffectedSystems and AffectedDepartments are also EF entities with which DowntimeEventModel is linked with foreign keys with, you could try to explictly included them when you fetch your DowntimeEventModel results as such:
PowerteqContext.DowntimeEventModel.Include("DowntimeEventModel.AffectedSystems").Include("DowntimeEventModel.AffectedDepartments").ToList();

double relationship between entities in EntityFramework 6

My problem looks simple. I need to implement a relationships between items in the database. For example: relationship between entities like computer and software shows users that computer stores a specific software and similarly - a software is installed in the specific computer. I think I should implement an entity with source id and target id or something similar. I wrote some code using code first in EntityFramework 6. Here are two classes:
public class ConfigurationItem
{
public int Id { get; set; }
public String Name { get; set; }
public String DeploymentState { get; set; }
public String IncidentState { get; set; }
[DataType(DataType.MultilineText)]
public String Description { get; set; }
[DataType(DataType.MultilineText)]
public String Note { get; set; }
public virtual ICollection<Relationship> Relationship { get; set; }
}
public class Relationship
{
[Key]
public int RelationshipId { get; set; }
[ForeignKey("ConfigurationItem")]
public int SourceId { get; set; }
[ForeignKey("ConfigurationItem")]
public int TargetId { get; set; }
public String Type { get; set; }
public virtual ConfigurationItem Source { get; set; }
public virtual ConfigurationItem Target { get; set; }
}
This solution doesn't work. I need a tip or something what should I try to make it work properly. EF throws an error about foreign key:
The ForeignKeyAttribute on property 'SourceId' on type 'cms_1.Models.Relationship' is not valid. The navigation property 'ConfigurationItem' was not found on the dependent type 'cms_1.Models.Relationship'. The Name value should be a valid navigation property name.
When I try to resolve it EF throws an error about cascade deleting. I know how to disable it but I just don't want to. I need a proper solution with that feature but I think I don't know how to do a model representing given scenario.
Simply - I need to store two foreign keys from entity "A" in the entity "B". How is it possible?
from a quick review , I can tell that you need 3 tables :
first : Computer
second : Software
third : a table , lets call it ComputerSoftware which tell which software has in what computer ( or you can also see it - which computer use what software ), which has ComputerID column and SoftwareID column.
example (source)
class Country
{
public int Id { get; set; }
public virtual ICollection<CountryCurrency> CountryCurrencies { get; set; }
}
class Currency
{
public int Id { get; set; }
}
class CountryCurrency
{
[Key, Column(Order=0)]
public virtual int CountryId { get; set; }
[Key, Column(Order=1)]
public virtual int CurrencyId { get; set; }
public virtual Country Country { get; set; }
public virtual Currency Currency { get; set; }
}
Your issue could be that in the migration file creating those tables, it will have something like
.ForeignKey("dbo.Relationship", t => t.Id, cascadeDelete: true)
This will be set on both tables, ConfigurationItem and Relationship of their Primary Key fields. When you delete one, that config tells SQL Server to delete the relationships as well and the relationship probably has a cascadeDelete: true to the parent. This will cause the cyclical cascading delete issue you are experiencing.
After the migration has been generated, go in and change one or all to cascadeDelete: false and this will fix that issue. This is what EF generates by default if I recall.

EF Code first - Lazy Loading How to set up and access the joining table

public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public bool IsInStock { get; set; }
public string ImageUrl { get; set; }
public List<ProductOption> ProductOptions { get; set; }
public virtual Category Category { get; set; }
}
public class ProductOption
{
[Key]
public int Id { get; set; }
public string ProductOptionName { get; set; }
public string ProductOptionDescription { get; set; }
}
Now I know when your using Code First EF, so that the tables are created correctly. You need to do something like this.
modelBuilder.Entity<Product>().HasMany(p => p.ProductOptions).WithMany().Map(m =>
{
m.MapLeftKey("ProductId").MapRightKey("ProductOptionId").ToTable("SelectedProductOptionsInOrderedItem");
});
So....
Does this mean that if I do something like Product.ProductOptions I will be able to access all associated productoptions.
Is this the best way to set it up, or is there another way?
To enable lazy load and EF can create derived proxy types for your collection, that property should be declared this way:
public virtual ICollection<ProductOptions> ProductOptions { get; set; }
That should be enought. Other aspect is the mapping approach that you use. You choose fluent api, i prefer mapping by convention, but that is a matter of personal taste anyway.
Ok, Mapping by Conventions:
Is the ability of EF that from the name of entities and their properties along with their types, to map our model with the underlying data without providing any other information.
for example
public class Customer {
public long CustomerID {get; September;}
public string CustomerName {get; September;}
public Employee AssignedTo {get; September;}
}
With the previous model EF will map database with a table named Customer with:
. CustomerID bigint primary key column
. CustomerName nvarchar column
. Customer_EmployeeID foreign key to Employee table, with the datatype Corresponding to EmployeeID in that table.
You can read more Here

Updating FK relationships in Entity Framework 4.1

I think I have read every article and stack overflow question regarding this, but cannot work out the solution. Let me start out with my models
public class Entry
{
public Entry ()
{
DateEntered = DateTime.Now;
}
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
public string Number { get; set; }
public string FbId { get; set; }
[ReadOnly(true)]
public DateTime DateEntered { get; set; }
public string AccessToken { get; set; }
//Relationsips
public Backgrounds Background { get; set; }
public Cars Car { get; set; }
}
public class Backgrounds
{
public int Id { get; set; }
public string Name { get; set; }
public string Filename { get; set; }
}
public class Cars
{
public int Id { get; set; }
public string Name { get; set; }
public string FileName { get; set; }
}
Now in my controller, I am updating the entry. Like follows
// PUT /api/entries/5
public HttpResponseMessage Put(Entry entry)
{
if(ModelState.IsValid)
{
_db.Entries.Attach(entry);
_db.Entry(entry).State = EntityState.Modified;
_db.SaveChanges();
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
My Entry model gets updated correctly, but if for eg entry.Background.Name changes, this will not be persisted to the database. My controller is accepting the entire entry model including its relationships => Backgrounds and Cars. However any value that is changed to the relationship is not updated or reflected. Any elegant solution without having to query the database then updating? I dont want to have any extra queries or lookups before I update.
Thanks
Tyrone
You must manually tell EF about all changes done to the object graph. You told EF just about change to entry instance but you didn't tell it about any change to related entities or relations itself. There is no elegant way to solve this. You have generally two options:
You will use some DTOs instead your entities and these DTOs will have some flag like IsDirty - when you receive object graph back to your controller you will reconstruct entities from DTOs and set their state based on IsDirty. This solution needs further extensions for example if your client can also delete relations.
You will query object graph from database and merge your incoming changes to entities retrieved from database.
There are some partial solutions like forcing to save changes to all related objects by setting their state to modified and identifying new objects by Id == 0 but again these solutions work only in specific scenarios.
More complex discussion about this problem.

Categories