I am a bit confused about whether to use DbSet.Create, or simply new up an entity and add it. I don't really understand the ramifications of using DbSet.Create.
I understand that DbSet.Create will create a proxied version if applicable, but I don't really understand what that means. Why do I care? It seems to me that an empty Proxied class is no more useful than a non-proxied class, since there are no related entities to lazy load.
Can you tell me the difference, beyond the obvious? And why would you care?
A scenario where using DbSet<T>.Create() makes sense is attaching an existing entity to the context and then leverage lazy loading of related entities. Example:
public class Parent
{
public int Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
}
The following would work then:
using (var context = new MyDbContext())
{
var parent = context.Parents.Create();
parent.Id = 1; // assuming it exists in the DB
context.Parents.Attach(parent);
foreach (var child in parent.Children)
{
var name = child.Name;
// ...
}
}
Here lazy loading of children is triggered (perhaps with resulting empty collection, but not null). If you'd replace context.Parents.Create() by new Parent() the foreach loop will crash because parent.Children is always null.
Edit
Another example was here (populating a foreign key property of a new entity and then getting the navigation property lazily loaded after the new entity is inserted into the DB): Lazy loading properties after an insert
Related
I have entities set up something like this:
public class MyThing
{
public int Id { get; set; }
public virtual MyOtherThing { get;set; }
}
public class MyOtherThing
{
public int Id { get; set; }
public virtual MyThing MyThing { get; set; }
}
My intention is that 'MyThing' can have one or none of MyOtherThing, and I also want a navigation link from MyOtherThing to it's parent.
I have configured the following EntityBaseConfiguration for the 'MyOtherThing' entity:
this.HasOptional(x => x.MyThing)
.WithOptionalPrincipal(x => x.MyOtherThing);
I can assign and modify MyOtherThing to MyThing no problem, but when I want to unassign 'MyOtherThing' from 'MyThing', how do I do this?
I tried the following:
myThing.MyOtherThing = null;
and then editing the entity by setting the EntityState.Modified state, but this didn't remove the association between the entities.
I tried adding the following to my MyThing entity, but this resulted in an EF 'Multiplicity is not valid' error when updating my database model:
public int? MyOtherThingId{ get; set; }
Thanks in advance!
I tried the following:
myThing.MyOtherThing = null;
If you want to remove an optional dependent entity (here: MyOtherThing) from a principal entity (here MyThing) by setting it to null, you have to pull the entity from the database with the dependent entity included, for example:
var mything = context.MyThings.Include(m => m.MyOtherThing)
.Single(t => t.Id == idValue);
(It's also OK when the belonging MyOtherThing is loaded into the context later, for example by lazy loading).
Without Include, myThing.MyOtherThing already is null and EF doesn't detect any change. Note that the statement myThing.MyOtherThing = null; doesn't execute lazy loading, which is a bit confusing because with collections the behavior is different.
By the way, the dependent entity can also be removed from the database directly, which is more efficient.
var ot = context.Set<MyOtherThing>().Find(idValue);
context.Set<MyOtherThing>().Remove(ot);
context.SaveChanges();
How to configure a EF6 migration with a model class having?
A collection o items
A navigation property to one particular item
public class MyModel
{
[Key]
public int Id { get; set; }
// My collection of elements
public virtual ICollection<MyCollectionElement> MyCollection { get; set; }
// Optional navigation to a particular element from the collection
[ForeignKey("CurrentElement")]
public int? CurrentElementId { get; set; }
public virtual MyCollectionElement CurrentElement { get; set; }
}
public class MyCollectionElement
{
[Key]
public int Id { get; set; }
// Required navigation to MyClass
[ForeignKey("MyModel")]
public int MyModelID { get; set; }
public virtual MyModel Model { get; set; }
}
Configuration
modelBuilder.Entity<MyModel>()
.HasMany(x => x.MyCollection)
.WithRequired(x => x.Model)
.HasForeignKey(x => x.MyModelID)
.WillCascadadeOnDelete(false);
Throws several errors on Update-Database, like
Unable to determine a valid ordering for dependent operations.
I would like a solution which doesn't involve a boolean IsCurrent in MyCollectionElement to make another query later and find which element is the current; instead, I would like to store the current element's id with my model, like exposed.
Also, I don't mind making int CurrentElementId non nullable (required) if it's easier.
Thanks.
This chicken-and-egg problem always looms when there are circular relationships. The error...
Unable to determine a valid ordering for dependent operations.
...is not thrown when the database is created. The database can be created just fine. It occurs when you try to insert a MyModel record and a MyCollectionElement referring to one another in the same unit of work. In the Seed method you probably have something like
var element = new MyCollectionElement();
var model = new MyModel();
model.MyCollection.Add(element);
model.CurrentElement = element;
The statement model.MyCollection.Add(element); requires model to be inserted first, so element can refer to it in its foreign key. But model.CurrentElement = element; requires element to be inserted first.
You can only avoid this situation by calling SaveChanges twice, and wrapping everything in a TransactionScope if you want the assignments to be transactional:
using(var ts = new TransactionScope())
{
using(var db = new MyContext()
{
var element = new MyCollectionElement();
var model = new MyModel();
model.MyCollection.Add(element);
db.MyModels.Add(model);
db.SaveChanges();
model.CurrentElement = element;
db.SaveChanges();
}
ts.Complete();
}
This also means that int CurrentElementId should remain nullable.
I have a trouble with EF (6.1.3)
I have created next classes (with many-to-many relationship):
public class Record
{
[Key]
public int RecordId { get; set; }
[Required]
public string Text { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
[Key]
public int TagId { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Record> Records{ get; set; }
}
And method:
void AddTags()
{
Record[] records;
Tag[] tags;
using (var context = new AppDbContext())
{
records = context.Records.ToArray();
}//remove line to fix
tags = Enumerable.Range(0, 5).Select(x => new Tag()
{
Name = string.Format("Tag_{0}", x),
Records= records.Skip(x * 5).Take(5).ToArray()
}).ToArray();
using (var context = new AppDbContext()){ //remove line to fix
context.Tags.AddRange(tags);
context.SaveChanges();
}
}
If I use two contexts, the records (which were added to created tags) will be duplicated. If I remove marked rows - problem disappears.
Is there any way to fix this problem without using the same context?
If you can, better reload entities or not detach them at all. Using multiple context instances in application is overall making things much more complicated.
The problem for you comes from the Entity Framework entity change tracker. When you load entitites from your DbContext and dispose that context, entities get detached from entity change tracker, and Entity Framework has no knowledge of any changes made to it.
After you reference detached entity by an attached entity, it (detached entity) immediately gets into entity change tracker, and it has no idea that this entity was loaded before. To give Entity Framework an idea that this detached entity comes from the database, you have to reattach it:
foreach (var record in records) {
dbContext.Entry(record).State = EntityState.Unchanged;
}
This way you will be able to use records to reference in other objects, but if you have any changes made to these records, then all these changes will go away. To make changes apply to database you have to change state to Added:
dbContext.Entry(record).State = EntityState.Modified;
Entity Framework uses your mappings to determine row in database to apply changes to, specifically using your Primary Key settings.
A couple examples:
public class Bird
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
}
public class Tree
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class BirdOnATree
{
[Column(Order = 0), Key, ForeignKey("Bird")]
public int BirdId { get; set; }
public Bird Bird { get; set; }
[Column(Order = 1), Key, ForeignKey("Tree")]
public int TreeId { get; set; }
public Tree Tree { get; set; }
public DateTime SittingSessionStartedAt { get; set; }
}
Here's a small entity structure so that you could see how it works. You can see that Bird and Tree have simple Key - Id. BirdOnATree is a many-to-many table for Bird-Tree pair with additional column SittingSessionStartedAt.
Here's the code for multiple contexts:
Bird bird;
using (var context = new TestDbContext())
{
bird = context.Birds.First();
}
using (var context = new TestDbContext())
{
var tree = context.Trees.First();
var newBirdOnAtree = context.BirdsOnTrees.Create();
newBirdOnAtree.Bird = bird;
newBirdOnAtree.Tree = tree;
newBirdOnAtree.SittingSessionStartedAt = DateTime.UtcNow;
context.BirdsOnTrees.Add(newBirdOnAtree);
context.SaveChanges();
}
In this case, bird was detached from the DB and not attached again. Entity Framework will account this entity as a new entity, which never existed in DB, even though Id property is set to point to existing row to database. To change this you just add this line to second DbContext right in the beginning:
context.Entry(bird).State = EntityState.Unchanged;
If this code is executed, it will not create new Bird entity in DB, but use existing instead.
Second example: instead of getting bird from the database, we create it by ourselves:
bird = new Bird
{
Id = 1,
Name = "Nightingale",
Color = "Gray"
}; // these data are different in DB
When executed, this code will also not create another bird entity, will make a reference to bird with Id = 1 in BirdOnATree table, and will not update bird entity with Id = 1. In fact you can put any data here, just use correct Id.
If we change our code here to make this detached entity update existing row in DB:
context.Entry(bird).State = EntityState.Modified;
This way, correct data will be inserted to table BirdOnATree, but also row with Id = 1 will be updated in table Bird to fit the data you provided in the application.
You can check this article about object state tracking:
https://msdn.microsoft.com/en-US/library/dd456848(v=vs.100).aspx
Overall, if you can avoid this, don't use object state tracking and related code. It might come to unwanted changes that are hard to find source for - fields are updated for entity when you don't expect them to, or are not updated when you expect it.
Not sure about title of the question, anyways! I have my Model Class RandomStuff which has Environment as Virtual Property.
public class RandomStuff
{
//NOT SHOWN ON FORM HIDDEN JUST TO MAKE IT EASIER IN DB
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Newtonsoft.Json.JsonProperty]
public int JobId { get; set; }
[Required]
[ForeignKey("Environment")]
[Newtonsoft.Json.JsonProperty]
[DataMember(IsRequired = true)]
public int EnvironmentId { get; set; }
[Newtonsoft.Json.JsonProperty ]
public virtual Environment Environment { get; set; }
}
My Environment is as below:-
[Newtonsoft.Json.JsonObject(Newtonsoft.Json.MemberSerialization.OptIn)]
[DataContract]
public class Environment
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Newtonsoft.Json.JsonProperty]
public int Id { get; set; }
[Required]
[Newtonsoft.Json.JsonProperty]
[DataMember(IsRequired = true)]
public string Name { get; set; }
}
Now, in my controller I have :-
public HttpResponseMessage Save(RandomStuff stuff)
{
if (ModelState.IsValid)
{
//Some stuff here
db.RandomStuffs.Add(stuff);//When this excutes, it is
//inserting data in Environment table also.
db.SaveChanges();
}
}
Now, whenever i am adding stuff to my RandomStuff table, it is adding Environment also.
EDIT:
I'll try to explain with general example, which I took from ef-inserting-duplicate-parent-objects
public class Foo
{
public int FooId {get;set;}
public virtual ICollection<Bar> Bars {get;set;}
}
public class Bar
{
public int BarId {get;set;}
public strung BarName {get;set;}
}
Now, if above being my model structure. I am trying to add one Foo data to my DB.
db.Foo.Add(new Foo{
FooId=1,
Bars=new List<Bar>(){
new Bar{
BarId=2,
BarName="Something"
}
}
})//Something of this sort will be my data, which needs to be inserted
//Now this record "new Bar{BarId=2, BarName="Something"}" is already present in DB
Now, what is happening is, it is inserting data in Foo table with id=1 and it is also adding duplicate in Bars table with id 2.
Also if I use something like below, my requirement is fulfilled:
db.Entry(fooModelDataComingFromSomewhere.Bars).State = EntityState.Unchanged;
Your question still isn't quite clear, as you are now providing code snippets from another post that aren't even in the same structure as the code you are having an issue with. However, I'm still guessing that your issue is with duplicate inserts, so I'll try to provide a suggestion.
When you use the Add() Method on the Entity Framework DB Context, it marks the entire graph of connected objects as Added, even if the item already exists in the database. A common situation that you are probably seeing is that when you Add an object of type RandomStuff it is also adding a new object of type Environment, and though Environment.Name is the same, Environment.Id is Database assigned, and since Entity Framework has marked the entire graph as Added, it creates a new Environment object, with the same Name but a new Id. therefore, you have multiple Environment rows in your table with the same Name.
The solution is deceptively simple. Since you already have a Property on your RandomStuff object to hold the Foreign Key representing the Environment object, instead of adding a new Environment object to the graph, simply create your RandomStuff object and add the value of the EnvironmentId. In other words,
Instead of creating a new RandomStuff Object this way:
var RandomStuff randomStuff = new RandomStuff {
Environment = new Environment {
Name = "Existing Environment"
}
}
create it like this:
var RandomStuff randomStuff = new RandomStuff {
EnvironmentId = existingEnvironment.Id
}
I haven't been able to find someone else with this issue specifically so here goes.
I have a simple model where one entity simply references another as a parent-child or one-to-many relationship defined like this:
public class Parent
{
public int ID { get; private set; }
public string Name { get; private set; }
}
public class Child
{
public int ID { get; private set; }
public string Name { get; private set; }
public virtual Parent Parent { get; private set; }
}
I am creating speicific mapping files for each, which work great for all the normal properties except for the related entity. It is always coming up null. No matter whether i have the virtual/private accessors on the property it will not load UNLESS i pull a copy of the parent separately from the context first. My mapping looks like this:
HasRequired(t => t.Parent).WithMany().Map(t => t.MapKey("ParentID")).WillCascadeOnDelete();
Is there anything I am doing wrong with this? I cannot for the life of me figure this out. Just so I cover all the bases, I am loading the entity like this:
Context.Set<Child>().FirstOrDefault(x => x.ID == 1);
And lastly here are some constraints I have:
I cannot have the foreign keys in my model as properties.
I cannot have a collection of children from the parent.
I finally figured it out. After much trial and error I noticed that having a parameterless constructor marked as internal, EF cannot create its dynamic proxy class of your type and therefore disables all lazy loading. I have two contructors, one for EF to hydrate objects, and another with parameters requires for callers to create my entity. Once I changed the signature to protected internal it started working. So I changed this:
internal Child() {}
to
protected internal Child() {}
May be you hasn't enable lazy loading .Try this,
Context.Set<Child>().FirstOrDefault(x => x.ID == 1).Include(c=>c.Parent);