I came here with an Entity Framework problem which I'm struggling with for some time already. Let's describe it quickly. I have 2 models that are referencing to one model... and I don't really know how can I create the relationship with EF annotations.
First model:
public class ProcessedLog
{
[Key]
public int Id { get; set; }
// Some other data
public virtual LogLocation Location { get; set; }
}
Second model:
public class QueuedLog
{
[Key]
public int Id { get; set; }
// Some other data
public virtual LogLocation Location { get; set; }
}
And the model that I'm referencing to:
public class LogLocation
{
[Key]
public int Id { get; set; }
[ForeignKey("QueuedLog")]
public int QueuedLogId { get; set; }
[ForeignKey("ProcessedLog")]
public int ProcessedLogId { get; set; }
// Some other data
public virtual QueuedLog QueuedLog { get; set; }
public virtual ProcessedLog ProcessedLog { get; set; }
}
As you can see, I've already tried to do something but it's not working properly. I'm getting an error:
LogLocation_ProcessedLog_Source: : Multiplicity is not valid in Role 'LogLocation_ProcessedLog_Source' in relationship 'LogLocation_ProcessedLog'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be ''.
LogLocation_QueuedLog_Source: : Multiplicity is not valid in Role 'LogLocation_QueuedLog_Source' in relationship 'LogLocation_QueuedLog'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be ''.
It works only when I do typical one-to-one relationship - but this is not what i want.
Btw. this is my first post at StackOverflow so i would like to say hi to everyone! :) You're creating a great community, thanks for all your work!
Edit
The question is: how can i create these models and relationshiph so it would work like this:
I add new ProcessedLog to Db -> It adds a new LogLocation with ProcessedLogId equal to the related ProcessedLog and the QueuedLogId is NULL.
You can do it like this. Database wise I am not sure if this is a great solution... Though it does look like it will work for you.
It worked with EntityFramework 6.1.3 and SQL Server Express.
ProcessedLog.cs
public class ProcessedLog
{
[Key]
public int Id { get; set; }
// Some other data
public virtual LogLocation Location { get; set; }
}
QueuedLog.cs
public class QueuedLog
{
[Key]
public int Id { get; set; }
// Some other data
public virtual LogLocation Location { get; set; }
}
LogLocation.cs
public class LogLocation
{
[Key]
public int Id { get; set; }
// Some other data
public virtual QueuedLog QueuedLog { get; set; }
public virtual ProcessedLog ProcessedLog { get; set; }
}
Context.cs
public class Context : DbContext
{
public DbSet<ProcessedLog> ProcessedLogs { get; set; }
public DbSet<QueuedLog> QueuedLogs { get; set; }
public DbSet<LogLocation> LogLocations { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ProcessedLog>()
.HasRequired(p => p.Location)
.WithOptional(l => l.ProcessedLog);
modelBuilder.Entity<QueuedLog>()
.HasRequired(p => p.Location)
.WithOptional(l => l.QueuedLog);
}
}
Also the code I tested it with
using (var context = new Context())
{
var processedLog = new ProcessedLog
{
Location = new LogLocation()
};
var queuedLog = new QueuedLog
{
Location = new LogLocation()
};
context.ProcessedLogs.Add(processedLog);
context.QueuedLogs.Add(queuedLog);
context.SaveChanges();
}
Related
I have two models that both have a many-to-one relation and a many-to-many relation.
A user can create many alarms but an alarm can only have one creator.
A user can recieve many alarms and alarms have many recievers.
public class AppUser
{
public Guid Id { get; set; }
public virtual IEnumerable<Alarm> CreatedAlarms { get; set; }
[InverseProperty("AlarmRecievers")]
public virtual IEnumerable<Alarm> RecievedAlarms { get; set; }
public AppUser()
{
CreatedAlarms = new HashSet<Alarm>();
RecievedAlarms = new HashSet<Alarm>();
}
}
and
public class Alarm
{
public Guid Id { get; set; }
[ForeignKey("AppUser")]
public Guid? AppUserId { get; set; }
public AppUser AppUser { get; set; }
[InverseProperty("RecievedAlarms")]
public virtual IEnumerable<AppUser> AlarmRecievers { get; set; }
public Alarm()
{
AlarmRecievers = new HashSet<AppUser>();
}
}
but when I try to add a migrations, I get the title as an error. I did expect there to be a created a AlarmReciever table but considering I don't have the model in my code I don't know how to create the key.
Earlier I had
modelBuilder.Entity<AlarmReciever>()
.HasKey(c => new { c.AppUserId, c.AlarmId });
but I'm trying to get rid of the join table in my code so I'd like to do it the way I'm trying to
My database has two tables - RuleGroups and Rules. My Entity Framework classes are the following:
public class RuleGroup
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Rule> Rules { get; set; }
}
public class Rule
{
[Key]
public Guid Id { get; set; }
public Guid RuleGroupId { get; set; }
public string Name { get; set; }
public ICollection<Condition> Conditions { get; set; }
[ForeignKey("RuleGroupId")]
public virtual RuleGroup RuleGroup { get; set; }
}
[NotMapped]
public class Condition
{
public Guid Id { get; set; }
public string Name { get; set; }
}
Class Condition is not mapped because it is being serialized and stored as JSON in Rule Table (using this example)
My DTOS are the following:
public class UpdateRuleGroupDto
{
public string Name { get; set; }
public ICollection<UpdateRuleDto> Rules { get; set; }
}
public class UpdateRuleDto
{
public string Name { get; set; }
public ICollection<UpdateConditionDto> Conditions { get; set; }
}
public class UpdateConditionDto
{
public string Name { get; set; }
}
In my Startup.cs I initialize Automapper :
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<UpdateRuleGroupDto, RuleGroup>();
cfg.CreateMap<UpdateRuleDto, Rule>();
cfg.CreateMap<UpdateConditionDto, Condition>();
}
I have an API controller endpoint that accepts JSON PATCH document to make changes to data stored in database.
public IActionResult Patch(Guid ruleGroupId, [FromBody]JsonPatchDocument<UpdateRuleGroupDto> body)
{
RuleGroup ruleGroupFromRepo = _deviceRules.GetRuleGroup(ruleGroupId);
UpdateRuleGroupDto ruleGroupToPatch = Mapper.Map<UpdateRuleGroupDto>(ruleGroupFromRepo);
// Patching logic here
Mapper.Map(ruleGroupToPatch, ruleGroupFromRepo);
context.SaveChanges();
return NoContent();
}
The problem:
When changes are made/saved, Rules in Rule table change their/get new GUID.
Example, say we have this data in 2 Tables.
RuleGroup Table
[Id][Name]
[ddad5cac-e5a1-4db7-8167-66a6de3b8a0c][Test]
Rule Table
[Id][RuleGroupId][Name][Condition]
[17c38ee8-4158-4ecc-b893-97786fa76e13][ddad5cac-e5a1-4db7-8167-66a6de3b8a0c][Test][[{"Name":"Test"}]]
If I change field [Name] to a new value, Rules Table will look like this.
Rule Table
[Id][RuleGroupId][Name][Condition]
[ba106de8-bcbc-4170-ba56-80fe619cd757][ddad5cac-e5a1-4db7-8167-66a6de3b8a0c][Test2][[{"Name":"Test"}]]
Note that [Id] field has now a new GUID.
EDIT
#Gert Arnold made me realize that I'm not attaching entities.
I ran the following code:
foreach (var item in ruleGroupFromRepo.rules)
{
var x = _context.Entry(item).State;
}
and all the states were Added and not modified. Now I just have to figure out how to do it properly.
I have a Project model which has a ProjectLead (one instance of the Person Foreign Key), this works fine. But now I also need to add a collection of People (Project members) referencing the same Person table and I can't get the Entity Framework to generate my database. As soon as I try to add the Fluent API code to create the link table ProjectPerson I get an error - "Schema specified is not valid. Errors: The relationship 'MyApp.WebApi.Models.Person_Projects' was not loaded because the type 'MyApp.WebApi.Models.Person' is not available." I assume this is because of the existing FK relationship already in place with ProjectLead.
Project Model:
public class Project
{
[Key]
public int ProjectId { get; set; }
public string Name { get; set; }
// Foreign Key - Project lead (Person)
public int ProjectLeadId { get; set; }
public virtual Person ProjectLead { get; set; }
// Create many to many relationship with People - Team members on this project
public ICollection<Person> People { get; set; }
public Project()
{
People = new HashSet<Person>();
}
}
Person Model:
public class Person
{
[Key]
public int PersonId { get; set; }
public String Firstname { get; set; }
public String Surname { get; set; }
// Create many to many relationship
public ICollection<Project> Projects { get; set; }
public Person()
{
Projects = new HashSet<Project>();
}
}
DB Context:
public class HerculesWebApiContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Project> Projects { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// This works fine
modelBuilder.Entity<Project>()
.HasRequired(c => c.ProjectLead)
.WithMany(d => d.Projects)
.HasForeignKey(c => c.ProjectLeadId)
.WillCascadeOnDelete(false);
// Adding these lines to create the link table `PersonProjects` causes an error
//modelBuilder.Entity<Person>().HasMany(t => t.Projects).WithMany(t => t.People);
//modelBuilder.Entity<Project>().HasMany(t => t.People).WithMany(t => t.Projects);
}
}
I gather that perhaps I need to use the InverseProperty attribute, but I am not sure where this should go in this case?
Can you explicitly define your join table? So, define a ProjectPeople relationship and make the code something like this...
public class ProjectPerson{
[Key]
public int ProjectPersonId { get; set; }
[ForeignKey("Project")]
public int? ProjectId {get;set;}
public virtual Project {get;set;}
[ForeignKey("Person")]
public int? PersonId {get;set;}
public virtual Person {get;set;}
public string RelationshipType {get;set;}
}
Then your other 2 classes will look like this...
public class Project
{
[Key]
public int ProjectId { get; set; }
public string Name { get; set; }
// Foreign Key - Project lead (Person)
public int ProjectLeadId { get; set; }
public virtual Person ProjectLead { get; set; }
// Create many to many relationship with People - Team members on this project
public virtual ICollection<ProjectPerson> ProjectPeople { get; set; }
public Project()
{
ProjectPerson = new HashSet<ProjectPerson>();
}
}
And this..
Public class Person
{
[Key]
public int PersonId { get; set; }
public String Firstname { get; set; }
public String Surname { get; set; }
// Create many to many relationship
public virtual ICollection<ProjectPerson> ProjectPeople { get; set; }
public Person()
{
ProjectPerson = new HashSet<ProjectPerson>();
}
}
I'm building an MVC5 project using Code First migrations with EF6 in Visual Studio 2013. Everything has been working as expected up to now: multiple migrations adding multiple tables to my db. After add a new class that should map to a new database table, add-migration won't pick up the new class and the new migration file is generated with no code in the Up() and Down() methods. The class in question is as simple as it can be:
[Table("SurveyItemOption")]
public class SurveyItemOption
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Option { get; set; }
public virtual SurveyItem SurveyItem { get; set; }
}
As you can see, I've added the DatabaseGenerated attribute myself to see if that makes any difference, but it still doesn't pick up the new class and create the new table. SurveyITem is the parent class and its db table is generated with Code First, as is the Survey class which is the parent of SurveyItem. Below is the other code that is successfully generated.
public enum ListType
{
None = 0,
Circle = 1,
Square = 2,
LowerAlpha = 3,
LowerLatin = 4,
LowerRoman = 5,
UpperAlpha = 6,
UpperLatin = 7,
UpperRoman = 8
}
[Table("Survey")]
public class Survey
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Title { get; set; }
public bool IsActive { get; set; }
public string Owner_UserId { get; set; }
public int Module_Id { get; set; }
public virtual IEnumerable<SurveyItem> Items { get; set; }
}
[Table("SurveyItem")]
public class SurveyItem
{
public SurveyItem() {
this.OptionListType = ListType.None;
}
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Item { get; set; }
public int Survey_Id { get; set; }
[EnumDataType(typeof(ListType))]
public ListType OptionListType { get; set; }
public virtual int OptionListTypeId
{
get
{
return (int)this.OptionListType;
}
set
{
this.OptionListType = (ListType)value;
}
}
}
[Table("SurveyAnswer")]
public class SurveyAnswer
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string UserId { get; set; }
public string Answer { get; set; }
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Computed)]
public DateTime Created { get; set; }
public virtual SurveyItem SurveyItem { get; set; }
}
I've gone as far as creating a new project and imported all my Models, Views and Controllers (along with various Content files) from this project, built the project, and enabled migrations. The SurveyItemOption class is still ignored in the initial migration code.
Please help! Any ideas on how to fix this would be much appreciated!
I'm sure there's a good technical explanation for why this table was not getting generated by Code First, but I realized something was lacking in the relationship between SurveyItem, SurveyItemOption and SurveyAnswer (which stores the answers for a user). Initially, I was not going to include item options so I only had a relationship between SurveyItem and SurveyAnswer. SurveyAnswer had a foreign key to SurveyItem so my guess is EF didn't like that I was creating the same relationship on a different table. I also realized that SurveyAnswer needed a foreign key to SurveyItemOption in order to store a SurveyItemOption as the answer. Adding this relationship solved my problem.
[Table("SurveyAnswer")]
public class SurveyAnswer
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string UserId { get; set; }
public string Answer { get; set; }
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Computed)]
public DateTime Created { get; set; }
public virtual SurveyItem SurveyItem { get; set; }
public virtual SurveyItemOption SurveyItemOption { get; set; }
}
UPDATE:
It seems that another requirement is to make sure you have a DbSet set up on your data context, too.
Using code first, I have some abstract classes and some classes derived from those abstracted classes.
// Abstracted Classes
public abstract class Brand
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public abstract class Model
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
// Derived Classes
[Table("ComparisonBrand")]
public class ComparisonBrand : Brand
{
public ComparisonBrand()
{
ComparisonValues = new List<ComparisonValue>();
Models = new List<ComparisonModel>();
}
public virtual ICollection<ComparisonValue> ComparisonValues { get; set; }
public virtual ICollection<ComparisonModel> Models { get; set; }
}
[Table("ComparisonModel")]
public class ComparisonModel : Model
{
public int? BrandId { get; set; }
public int? LogoId { get; set; }
[ForeignKey("BrandId")]
public virtual ComparisonBrand ComparisonBrand { get; set; }
[ForeignKey("LogoId")]
public virtual ComparisonLogo ComparisonBrand { get; set; }
public virtual ICollection<ComparisonValue> ComparisonValues { get; set; }
}
My issue is that the migration generates foreign keys for:
ComparisonModel.Id > Models.Id
ComparisonModel.BrandId > Brands.Id
ComparisonModel.BrandId > ComparisonBrand.Id
Since ComparisonBrand.Id is a FK to Brands.BrandId, I get an error when deleting a Brand record. If I delete the ComparisonModel.BrandId > ComparisonBrand.Id relationship, however, the delete works fine.
How can I prevent a relationship from being formed between both the abstracted table and the derived table (Brands and ComparisonBrand)?
You are using the virtual keyword this causes Lazy Loading. You are telling EF to generate Foreign keys for them through this feature. Drop the virtual and you will not create the keys any longer