I need to insert a new entity without knowing it's PK. The parent entity has another property which is a guid and unique which is what we use to do cross db references and this is all I have. I have done it in the past but can't find a reference on how to do it again.
[Table("School")]
public class SchoolEntity
{
public SchoolEntity()
{
Students = new HashSet<StudentEntity>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public Guid ExternalId { get; set; }
[ForeignKey("SchoolId")]
public virtual ICollection<StudentEntity> Students { get; set; }
}
[Table("Student")]
public class StudentEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public SchoolEntity School { get; set; }
}
//ExternalId won't work cause is not the primary key.
var school = new School { ExternalId = Guid.Parse('68e05258-550a-40f3-b68a-5d27a0d825a0') };
context.Attach(school);
context.Schools.Add.Add(new Student());
context.SaveChanges();
Well, the PK of the referenced entity is required in order to set properly the FK of the referencing entity.
If you don't have it, apparently you should find it (get it from the database) based on what you have (secondary identifier in your case). For instance:
var school = context.Schools.Single(e => e.ExternalId == externalId);
var student = new Student { School = school, ... };
context.Students.Add(student);
context.SaveChanges();
There is no way to get that working without fetching. If you don't want to fetch the whole referenced entity (and you are sure it's not tracked by the context), then you can fetch the PK only and Attach a stub entity:
var schoolId = context.Schools.Where(e => e.ExternalId == externalId)
.Select(e => e.Id).Single();
var school = new School( Id = schoolId);
context.Attach(school);
// ...
Related
I have a problem with the Entity Framework.
public class User : Receiver
{
public User()
{
if (Groups == null)
Groups = new List<Group>();
if (Buddies == null)
Buddies = new List<User>();
}
[Required]
public string PhoneNumber { get; set; }
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
[ForeignKey("Guid"), JsonIgnore]
public IList<Group> Groups { get; set; }
}
public class Receiver
{
public Receiver()
{
Guid = Guid.NewGuid();
Created = DateTime.Now;
}
[Key]
public Guid Guid { get; set; }
[Required]
public DateTime Created { get; set; }
}
When i try to add a user...
User user = new User
{
Guid = new Guid("8cd094c9-e4df-494e-b991-5cf5cc03d6e3"),
PhoneNumber = "+4991276460"
};
cmc.Receivers.Add(user);
... it ends in follogwing error.
The object of the Type "System.Collections.Generic.List`1[Project.Models.User]" can't be converted to "Project.Models.User".
When i comment out following two lines:
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
...the programm runs fine.
I hope someone can help me to fix this problem.
Otherwise it runs into an error at this line : cmc.Receivers.Add(user);
In your mapping...
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
...you specify that User.Buddies is part of a one-to-many relationship and that User.Guid (=Receiver.Guid) is the foreign key in this relationship. But User.Guid is also the primary key, hence it must be unique. As a result a User cannot have a list of Buddies but only a single reference.
The mapping makes no sense but the exception is not very helpful and difficult to understand. (Somehow EF seems to recognize internally that the Buddies cannot be a list with that mapping and wants to cast the list to a single reference. It should detect in my opinion that the mapping is invalid in the first place.)
For a correct one-to-many mapping you need a foreign key that is different from the primary key. You can achieve that by either removing the [ForeignKey] annotation altogether...
[JsonIgnore]
public IList<User> Buddies { get; set; }
...in which case EF will create a default foreign key in the Receivers table (it will be some column with an underscore in its name, but you can rename that with Fluent API if you don't like the default name) or by adding your own foreign key property to the User class:
public Guid? BuddyGuid { get; set; }
[ForeignKey("BuddyGuid"), JsonIgnore]
public IList<User> Buddies { get; set; }
Using ASP.Net/C# with MVC4 and EF 5. I have a base model in which several other models are linked to via the id. For example
public class Person
{
public int Id { get; set; }
public DateTime? Created { get; set; }
public DateTime? Submitted { get; set; }
public DateTime? Finalized { get; set; }
public string UserProfileSite { get; set; }
public int UserId { get; set; }
public virtual ICollection<Demographic> Demographics { get; set; }
public virtual ICollection<Demographic> Demographics { get; set; }
}
The Demographics have a foreign key with the personId. When they add a person they get sent to fill out the demographics information. However since the demographics have a foriegn key of personId I need to create the person record.
I can create the person before going to the demographics view as follows:
Person person = new Person();
person.UserId = curuser.UserId;
person.UserProfileSite = curuser.Site;
person.Created = DateTime.Now;
db.Persons.Add(person);
db.SaveChanges();
How do I get the personId of the record I just saved to the database so I can pass it to my Demographics so the model has the foreign key of personId?
Assuming you have not turned of changeTracking within entity framework, you should be able to simply get the id of the object after you save it to the database
Person person = new Person();
person.UserId = curuser.UserId;
person.UserProfileSite = curuser.Site;
person.Created = DateTime.Now;
db.Persons.Add(person);
db.SaveChanges();
var personId = person.Id;
Although the link tables which facilitate a many-to-many relationship are usually hidden by EF, I have an instance where I think I need to create (and manage) one myself:
I have the following entities:
public class TemplateField
{
public int Id
{
get;
set;
}
[Required]
public string Name
{
get;
set;
}
}
public class TemplateFieldInstance
{
public int Id
{
get;
set;
}
public bool IsRequired
{
get;
set;
}
[Required]
public virtual TemplateField Field
{
get;
set;
}
[Required]
public virtual Template Template
{
get;
set;
}
}
public class Template
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
public virtual ICollection<TemplateFieldInstance> Instances
{
get;
set;
}
}
So essentially; a Template can have many TemplateField and a TemplateField can have many Template.
I believe I could just add a navigation property in the form of a collection of Template items on the TemplateField entity and have EF manage the link entity, but I need to store some additional information around the relationship, hence the IsRequired property on TemplateFieldInstance.
The actual issue I'm having is when updating a Template. I'm using code similar to the following:
var template = ... // The updated template.
using (var context = new ExampleContext())
{
// LoadedTemplates is just Templates with an Include for the child Instances.
var currentTemplate = context.LoadedTemplates.Single(t => t.Id == template.Id);
currentTemplate.Instances = template.Instances;
context.Entry(currentTemplate).CurrentValues.SetValues(template);
context.SaveChanges();
}
However; if I try and update a Template to - for example - remove one of the TemplateFieldInstance entities, it this throws an exception (with an inner exception) which states:
A relationship from the 'TemplateFieldInstance_Template'
AssociationSet is in the 'Deleted' state. Given multiplicity
constraints, a corresponding 'TemplateFieldInstance_Template_Source'
must also in the 'Deleted' state.
After doing some research, it sounds like this is because EF has essentially marked the TemplateFieldInstance foreign key to the Template as being null and then tried to save it, which would violate the Required constraint.
I'm very new to Entity Framework, so this is all a bit of a journey of discovery for me, so I'm fully anticipating there being errors in my approach or how I'm doing the update!
Thanks in advance.
You must map the relationships in your model as two one-to-many relationships. The additional field in the link table makes it impossible to create a many-to-many relationship. I would also recommend to use a composite key in your "link entity" TemplateFieldInstance where both components are foreign keys to the other entities. This ensures in the database that you can only have one row for a unique combination of a template field and a template and comes closest to the idea of a "many-to-many link table with additional data":
public class TemplateField
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<TemplateFieldInstance> Instances { get; set; }
}
public class TemplateFieldInstance
{
[Key, Column(Order = 0)]
public int FieldId { get; set; }
[Key, Column(Order = 1)]
public int TemplateId { get; set; }
public bool IsRequired { get; set; }
public virtual TemplateField Field { get; set; }
public virtual Template Template { get; set; }
}
public class Template
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<TemplateFieldInstance> Instances { get; set; }
}
EF naming conventions will detect the FK relations in this model if you use the property names above.
More details about such a model type are here: https://stackoverflow.com/a/7053393/270591
Your approach to update the template is not correct: context.Entry(currentTemplate).CurrentValues.SetValues(template); will only update the scalar fields of the template, not the navigation properties nor will it add or remove any new or deleted child entities of the parent entity. Unfortunately updating detached object graphs doesn't work that easy and you have to write a lot more code, something like this:
var template = ... // The updated template.
using (var context = new ExampleContext())
{
// LoadedTemplates is just Templates with an Include for the child Instances.
var currentTemplate = context.LoadedTemplates
.Single(t => t.Id == template.Id);
context.Entry(currentTemplate).CurrentValues.SetValues(template);
foreach (var currentInstance in currentTemplate.Instances.ToList())
if (!template.Instances.Any(i => i.Id == currentInstance.Id))
context.TemplateFieldInstances.Remove(currentInstance); // DELETE
foreach (var instance in template.Instances)
{
var currentInstance = currentTemplate.Instances
.SingleOrDefault(i => i.Id == instance.Id);
if (currentInstance != null)
context.Entry(currentInstance).CurrentValues.SetValues(instance);
// UPDATE
else
currentTemplate.Instances.Add(instance); // INSERT
}
context.SaveChanges();
}
A similar example with more comments what is happening is here: https://stackoverflow.com/a/5540956/270591
I am using EF 4.1 code first and I am struggling with the association entity and getting the value that was set in the association table. I tried to follow the post on: Create code first, many to many, with additional fields in association table.
My tables are as follows (all are in plural form):
Table: Products
Id int
Name varchar(50)
Table: Specifications
Id int
Name varchar(50)
Table: ProductSpecifications
ProductId int
SpecificationId int
SpecificationValue varchar(50)
My related classes:
public class Product : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductSpecification> ProductSpecifications { get; set; }
}
public class Specification : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductSpecification> ProductSpecifications { get; set; }
}
public class ProductSpecification
{
public int ProductId { get; set; }
public virtual Product Product { get; set; }
public int SpecificationId { get; set; }
public virtual Specification Specification { get; set; }
public string SpecificationValue { get; set; }
}
My context class:
public class MyContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Specification> Specifications { get; set; }
public DbSet<ProductSpecification> ProductSpecifications { get; set; }
protected override void OnModelCreating(DbModelBuilder dbModelBuilder)
{
}
}
My repository method where I do my call (not sure if it is correct):
public class ProductRepository : IProductRepository
{
MyContext db = new MyContext();
public Product GetById(int id)
{
var product = db.Products
.Where(x => x.Id == id)
.Select(p => new
{
Product = p,
Specifications = p.ProductSpecifications.Select(s => s.Specification)
})
.SingleOrDefault();
return null; // It returns null because I don't know how to return a Product object?
}
}
Here is the error that I am getting back:
One or more validation errors were detected during model generation:
System.Data.Edm.EdmEntityType: : EntityType 'ProductSpecification' has no key defined. Define the key for this EntityType.
System.Data.Edm.EdmEntitySet: EntityType: EntitySet �ProductSpecifications� is based on type �ProductSpecification� that has no keys defined.
What does it mean that no keys are defined? Won't the ProductId and SpecificationId map to Id of Product and Id of Specification respectively?
How would I return a single product with the all the specifications for it?
Entity Framework will recognize that ProductId is a foreign key property for the Product navigation property and SpecificationId is a foreign key property for the Specification navigation property. But the exception complains about a missing primary key ("Key" = "Primary Key") on your ProductSpecification entity. Every entity needs a key property defined. This can happen either by conventions - by a specific naming of the key property - or explicity with data annotations or Fluent API.
Your ProductSpecification class doesn't have a property which EF would recognize as a key by convention: No Id property and no ProductSpecificationId (class name + "Id").
So you must define it explicitely. Defining it with data annotations is shown in the post you linked:
public class ProductSpecification
{
[Key, Column(Order = 0)]
public int ProductId { get; set; }
public virtual Product Product { get; set; }
[Key, Column(Order = 1)]
public int SpecificationId { get; set; }
public virtual Specification Specification { get; set; }
public string SpecificationValue { get; set; }
}
And in Fluent API it would be:
modelBuilder.Entity<ProductSpecification>()
.HasKey(ps => new { ps.ProductId, ps.SpecificationId });
Both ways define a composite key and each of the parts is a foreign key to the Product or Specification table at the same time. (You don't need to define the FK properties explicitely because EF recognizes it due to their convention-friendly names.)
You can return a product including all specifications with eager loading for example:
var product = db.Products
.Include(p => p.ProductSpecifications.Select(ps => ps.Specification))
.SingleOrDefault(x => x.Id == id);
When using the CTP 5 of Entity Framework code-first library (as announced here) I'm trying to create a class that maps to a very simple hierarchy table.
Here's the SQL that builds the table:
CREATE TABLE [dbo].[People]
(
Id uniqueidentifier not null primary key rowguidcol,
Name nvarchar(50) not null,
Parent uniqueidentifier null
)
ALTER TABLE [dbo].[People]
ADD CONSTRAINT [ParentOfPerson]
FOREIGN KEY (Parent)
REFERENCES People (Id)
Here's the code that I would hope to have automatically mapped back to that table:
class Person
{
public Guid Id { get; set; }
public String Name { get; set; }
public virtual Person Parent { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
class FamilyContext : DbContext
{
public DbSet<Person> People { get; set; }
}
I have the connectionstring setup in the app.config file as so:
<configuration>
<connectionStrings>
<add name="FamilyContext" connectionString="server=(local); database=CodeFirstTrial; trusted_connection=true" providerName="System.Data.SqlClient"/>
</connectionStrings>
</configuration>
And finally I'm trying to use the class to add a parent and a child entity like this:
static void Main(string[] args)
{
using (FamilyContext context = new FamilyContext())
{
var fred = new Person
{
Id = Guid.NewGuid(),
Name = "Fred"
};
var pebbles = new Person
{
Id = Guid.NewGuid(),
Name = "Pebbles",
Parent = fred
};
context.People.Add(fred);
var rowCount = context.SaveChanges();
Console.WriteLine("rows added: {0}", rowCount);
var population = from p in context.People select new { p.Name };
foreach (var person in population)
Console.WriteLine(person);
}
}
There is clearly something missing here. The exception that I get is:
Invalid column name 'PersonId'.
I understand the value of convention over configuration, and my team and I are thrilled at the prospect of ditching the edmx / designer nightmare --- but there doesn't seem to be a clear document on what the convention is. (We just lucked into the notion of plural table names, for singular class names)
Some guidance on how to make this very simple example fall into place would be appreciated.
UPDATE:
Changing the column name in the People table from Parent to PersonId allows the Add of fred to proceed. Howerver you'll notice that pebbles is a added to the Children collection of fred and so I would have expected pebbles to be added to the database as well when Fred was added, but such was not the case. This is very simple model, so I'm more than a bit discouraged that there should be this much guess work involved in getting a couple rows into a database.
You need to drop down to fluent API to achieve your desired schema (Data annotations wouldn't do it). Precisely you have an Independent One-to-Many Self Reference Association that also has a custom name for the foreign key column (People.Parent). Here is how it supposed to get done with EF Code First:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasOptional(p => p.Parent)
.WithMany(p => p.Children)
.IsIndependent()
.Map(m => m.MapKey(p => p.Id, "ParentID"));
}
However, this throws an InvalidOperationException with this message Sequence contains more than one matching element. which sounds to be a CTP5 bug as per the link Steven mentioned in his answer.
You can use a workaround until this bug get fixed in the RTM and that is to accept the default name for the FK column which is PersonID. For this you need to change your schema a little bit:
CREATE TABLE [dbo].[People]
(
Id uniqueidentifier not null primary key rowguidcol,
Name nvarchar(50) not null,
PersonId uniqueidentifier null
)
ALTER TABLE [dbo].[People] ADD CONSTRAINT [ParentOfPerson]
FOREIGN KEY (PersonId) REFERENCES People (Id)
GO
ALTER TABLE [dbo].[People] CHECK CONSTRAINT [ParentOfPerson]
GO
And then using this fluent API will match your data model to the DB Schema:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasOptional(p => p.Parent)
.WithMany(p => p.Children)
.IsIndependent();
}
Add a new Parent record containing a Child:
using (FamilyContext context = new FamilyContext())
{
var pebbles = new Person
{
Id = Guid.NewGuid(),
Name = "Pebbles",
};
var fred = new Person
{
Id = Guid.NewGuid(),
Name = "Fred",
Children = new List<Person>()
{
pebbles
}
};
context.People.Add(fred);
context.SaveChanges();
}
Add a new Child record containing a Parent:
using (FamilyContext context = new FamilyContext())
{
var fred = new Person
{
Id = Guid.NewGuid(),
Name = "Fred",
};
var pebbles = new Person
{
Id = Guid.NewGuid(),
Name = "Pebbles",
Parent = fred
};
context.People.Add(pebbles);
var rowCount = context.SaveChanges();
}
Both codes has the same effect and that is adding a new parent (Fred) with a child (Pebbles).
In Entity Framework 6 you can do it like this, note public Guid? ParentId { get; set; }. The foreign key MUST be nullable for it to work.
class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid? ParentId { get; set; }
public virtual Person Parent { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
https://stackoverflow.com/a/5668835/3850405
It should work using a mapping like below:
class FamilyContext : DbContext
{
public DbSet<Person> People { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Person>().HasMany(x => x.Children).WithMany().Map(y =>
{
y.MapLeftKey((x => x.Id), "ParentID");
y.MapRightKey((x => x.Id), "ChildID");
});
}
}
However that throws an exception: Sequence contains more than one matching element.
Apperently that is a bug.
See this thread and the answer to #shichao question: http://blogs.msdn.com/b/adonet/archive/2010/12/06/ef-feature-ctp5-fluent-api-samples.aspx#10102970
class Person
{
[key()]
public Guid Id { get; set; }
public String Name { get; set; }
[ForeignKey("Children")]
public int? PersonId {get; set;} //Add ForeignKey
public virtual Person Parent { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
builder.Entity<Menu>().HasMany(m => m.Children)
.WithOne(m => m.Parent)
.HasForeignKey(m => m.PersonId);