I have a complex graph with a collection child entities.The relationship between parent and child entities is one-many relationship and has an Independent association. I know that I cant persist my graph without manual synchronization which I want to avoid. I found GraphDiff solution promising and I started exploring it but I couldn't achieve what I intend to as my scenario is somewhat peculiar. This issue is purely related to GraphDiff but I also welcome other solutions.
public class person
{
public virtual int Id {get; set;} //primary key
public virtual string Name {get; set;}
public virtual ICollection<Car> Cars {get; set;}
}
public class Car
{
public virtual int PersonId {get; set;} // is a foreign key
public virtual Datetime BoughtDate {get; set;} // this property and above mentioned foreign key together makes a primary key in database
public virtual DateTime InsuraceStartDate {get; set;}
public virtual DateTime InsuraceExpiryDate {get; set;}
public virtual DateTime LastServiceDate {get; set;}
}
At any given time my Person.Cars collection may have
1. A new car object.
2. An existing car object with updated values.(insurance or service dates).
Id property on Person and PersonId on Car will be 0.
Person _person = GetPersonToAddOrUpDate();
int id = _person.id; //id will be 0.
At this point we are not sure the object we received has to added or updated. PersonId on child entities(Cars) will also be 0.Now if we call.
Context.UpdateGraph(_person, m => m.OwnedCollection(x => x.Cars));
throws exception at FindEntityMatching(entity).
To overcome this I have to update Id property on person.
var dbPersonId = Context.Single<Person>(x => x.Name == _person.Name).Id;
_person.id = dbPersonId != null ? dbPersonId : 0;
Context.UpdateGraph(_person, m => m.OwnedCollection(x => x.Cars));
It deletes all entities in dbPerson.Cars and adds _person.Cars to dbPerson.Cars and saves. It means all records in my Cars table in database are deleted and records from _person.Cars are inserted. I noticed this is happening because Now the child entities does not have a Id property. I have to set them manually.
foreach(var car in _person.Cars)
car.PersonId = _person.Id
Now if you call
Context.UpdateGraph(_person, m => m.OwnedCollection(x => x.Cars));
This will add entities in collection from transient object missing in the persistent object and updates the object matching in the persistent object and deletes rest of the entities from the persistent object.
var dbCars = conext.Where(x => x.Personid == _person.Id).Select(x).ToList();
say dbCars.Count is 8 and _person.Cars.Count is 3.(one new car and 2 old cars with updated values)
Once you run.
Context.UpdateGraph(_person, m => m.OwnedCollection(x => x.Cars));
2 old cars from _person.Cars matching in dbCars will be updated.
1 new car from _person.Cars not matching in dbCars will be added.
Now the count of dbCars must be 9 as we added a new car. Now check the count.
var dbCarCount = conext.Where(x => x.Personid == _person.Id).Select(x).ToList().Count();
dbCarCount will be 3. Rest of the 6 cars from dbCars are being removed.
I wish I am wrong as I like to use your solution.My work in this area is stalled. Please let me know If I'm missing something or you need more information. I know my write up is confusing as I tried to put all my testing experience with GraphDiff. Hope you address this issue.
If you have a disconnected graph and you want to merge it to the database then there are 3 things it should do:
Insert new entities
Update existing entities
Delete entities not in the graph
You appear to be having an issue with the last part.
If the graph has not been retrieved from the database in the first place then you are not using GraphDiff as it was intended. You will have to do your adding and updating manually.
There are two tools I've found for that:
Trackable-Entities
Lightweight client-server facilities that enable caching and storing disconnected graphs.
Supports PCLs, Xamarin and other platforms
MIT license
Breeze#
Full-fledged client-server facilities that enable caching and storing disconnected graphs.
Not supported in UWP
Very low maintenance
MIT licenced
Related
I am defining a lot of classes in my project and as an audit requirement, every entity in my project has to have to foreign keys to the "AspNetUser" table, one specifying the user that created the record, and one for the user that has updated it. The following is a sample of "Customer" entity, however, as I said there is tons of entities all with the same requirement:
public class Customer {
public int Id {get; set;}
// bunch of properties
[Required]
public string CreateUserId {get; set;}
[Required]
public string UpdateUserId {get; set;}
[ForeignKey("CreateUserId")]
public virtual ApplicationUser CreateUser {get; set;}
[ForeignKey("UpdateUserId")]
public virtual ApplicationUser UpdateUser {get; set;}
}
Now because there is two foreign keys to the AspNetUser table, when I want to create migration and update the database I get the good old error:
Introducing FOREIGN KEY constraint 'FK_dbo.Customers_dbo.AspNetUsers_UpdateUserId' on table 'Customers' 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. See previous errors.
I also understand that by adding some codes to the "OnModelCreate" and remove the cascade effect for the "Customer" entity, I will be able to get around this, however, as I said, every entity in my application is going to have this two links to the AspNetUser table which means I have to add that piece of code to the OnModelCreate tons of times each time with the specific entity I'm talking about.
Also, I don't want to cancel the CASCADE DELETE effect as a whole, as it affects the functionality of the app in all of the parts. Any idea how can I efficiently get around this?
I remember back in the days when I did database programming, there was never any issues with one entity having two foreign keys to another one.
you should set WillCascadeOnDelete = False by fluentApi, Like This :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer >()
.HasRequired(c => c.CreateUser)
//.WithMany()
.HasForeignKey(c => c.CreateUserId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Customer>()
.HasRequired(c => c.UpdateUser )
//.WithMany()
.HasForeignKey(c => c.UpdateUserId )
.WillCascadeOnDelete(false);
}
for ( int i = 0; i < libraryList.Count; i++)
{
if (ModelState.IsValid)
{
context.Library.Add(libraryList[i]);
context.SaveChanges();
}
}
A library contains an entity 'predefinedgoals' which is already set up in the DB. So when the above code runs it stores dublicates of 'predefinedgoals' and assigns new ID's to them.
I read that I should attach the existing entity to the context but I'm not sure how to to do it in my scenario. The classes look like this:
class library
int libraryID
list<book> bks
.
class book
int bookID
list<importantdates> impdts
.
class importantdate
int importantdateID
predefinedgoal predfg
int numberofresellers
.
class predefinedgoal
int predefinedgoalID
string description
int daysfrompublication
I tried something like this right after ModelState.IsValid but I sense I'm doing it wrong:
var prdfgs= context.predefinedgoals.ToList();
foreach(var pg in prdfgs)
context.predefinedgoals.Attach(pg);
This answer is going to be based on a couple of assumptions, but I've seen this exact problem so many times that this is automatically my go-to answer.
What I think you're doing is that you're creating Library, Book, and ImportantDate objects (and setting up all of the relationships between them as well). In the process of doing all of this, however, you are trying to set the PreDefinedGoal navigational property on those ImportantDate objects, all the while leaving the actual int FK property (that would be something like PreDefinedGoalID), still set to 0. When that happens, Entity Framework disregards the fact that the object contained in the navigational property has an ID on it, and assumes that you are trying to create this PreDefinedGoal object from scratch, just like you're creating the ImportantDate object (as well as the others). It will then create a PreDefinedGoal object with the exact same data as the one you're actually trying to use, but it will create it as a separate, duplicate record in the database.
The solution to your problem then is simple: Don't set the navigational property. Just simply set the FK (ImportantDate.PreDefinedGoalID) to the ID of the PreDefinedGoal object that you want to hook up to it. When you do that, and you save it, Entity Framework will then reach out to the database for the correct object based on that ID, and thus you will avoid having duplicate PreDefinedGoal objects in your database.
FYI: I learned this from one of Julie Lerman's MSDN posts. If you're going to be working with EF, I highly recommend reading her posts and columns.
I am in the same situation and found a workaround. The way this workaround works makes me think that in this case EF is to blame for handling the situation badly.
In order to simplify the example I will just post an example with one object and it's navigational property.
public class Topic
{
int Id { get; set; }
public String Name { get; set; }
public String Description { get; set; }
}
public class Course
{
int Id { get; set; }
public Topic Topic { get; set; }
// additional properties don't matter now
}
Note the absence of any foreign key or other data annotations. EF6 will correctly create the database schema from this and infer that Id is the primary key.
Without workaround adding a new course for an existing topic will create a new topic object with a new Id (overwriting the Id it was given!) :
db.Courses.Add(course);
await db.SaveChangesAsync();
The braindead workaround:
course.topic = db.Topics.Find(course.topic.Id);
db.Courses.Add(course);
await db.SaveChangesAsync();
In other words, if the topic has been loaded from the context directly, EF will recognize it as an existing topic and don't try to add it again.
Update: To just attach the entity without reloading it:
db.Topics.Attach(course.topic);
However you will run into more issues with this setup, it is probably best to use ForeignKey attribute(s) and include the TopicId in Course object. Following works OK but still looks ridiculous to me:
[ForeignKey("Topic")]
public int TopicId { get; set; }
[ForeignKey("TopicId")]
public virtual Topic Topic { get; set; }
Would love to hear about a less redundant solution though.
The answer to why it stored duplicates in my scenario was that I performed tasks in two different classes - using different database context variables in each of them.
So class #1 is the one in my question, that's where I'm saving to the DB using context #1. In class #2 I retrieved all the PredefinedGoals and added them to ImportantDates but to do this I created context #2. The ID's and objects were the same but retrieved from different context variables.
I solved it by retrieving the PredefinedGoals in class #1 with context variable #1 and sent them as an argument to class #2.
I am sure this question has been asked before, so I apologize in advance, but am not sure of the correct keywords to include in my searches...
I am having trouble understanding the proper pattern for updating (or even inserting) an object when one of its properties is a collection of other properties in a disconnected environment (like a website). My issue has to do with the idea that a web application is only returning a collection of id's as opposed to the full object. I think the best way to explain this is with code snippets.
Given the following objects
Public Class User
Public Property UserId As Integer
Public Property Username As String
Public Property Roles As ICollection(Of Role)
End Class
Public Class Role
Public Property RoleId As Integer
Public Property RoleName As String
Public Property Users As ICollection(OF User)
End Class
Public Class EFDbContext
Inherits Entity.DbContext
Public Property Users As Entity.DbSet(Of User)
Public Property Roles As Entity.DbSet(Of Role)
End Class
A database is created with 3 tables - Users, Roles, and RoleUsers.
I know I can easily do the following
Dim db = New EFDbContext()
Dim r1 = New Role() With { .RoleName = "User" }
Dim r2 = New Role() With { .RoleName = "Admin" }
db.Roles.Add(r1)
db.Roles.Add(r2)
Dim u1 = New User() With { .UserName = "test1", .Roles = New List(Of Role) }
u1.Roles.Add(r1)
db.Users.Add(u1)
db.SaveChanges()
And it will save both new roles to the database (giving them RoleId values of 1 and 2 respectively), a new user (giving it a UserId value of 1) and a new Role-User entry with RoleId 1 and UserId 1.
However, when dealing with a disconnected scenario like a website, most people would have a View Model to represent the input from the user which then gets mapped back to the Entities. In addition, for values representing the Roles, the data coming back would most likely only contain the unique key representing the Role. For example,
Public Class UpdatedUserViewModel
Public Property UserId As Integer
Public Property Username As String
Public Property RoleIds As ICollection(Of Integer)
End Class
...
...
Dim userEntity = db.Users.Find(user.Values.UserId)
AutoMapper.Mapper.Map(userValues, userEntity)
So while the userEntity.Roles collection may contain a single item, the mapper probably just added the entry with something like
ForMember(Function(u) u.Roles, Sub(m) m.MapFrom(Function(su) su.RoleIds.Select(Function(r) New Role() With {.RoleId = r})))
And now we come to the problem, when the SaveChanges() method is called, EF throws a Validation error because the .RoleName property is Nothing.
How does this situation get handled? Are we supposed to manually loop through the Roles and fetch each one from the database? Can we not use mapping tools? Do I give bogus values for the "missing" properties and then loop through and mark them as Unchanged?
I know this was long but I thought the walk-throughs would be helpful...
Thanks.
You can use this algorithm
Start with the root entities.
For each root entity, e.g. a of type A, set a's properties except for navigation properties (at least all the mandatory ones (non-nullables))
Add the As to the context.
Next prepare child entities (entities that must have exactly 1 A) e.g. b of type B.
Set b's properties (except navigations, at least all non-nullables).
For each b, add b to its a (e.g. a.Children.Add(b)).
Continue with child entities of above
...
Save and apply changes
If you have an entity with a non-nullable navigation that already exists in DB and has not yet been accessed via context, you can set the relationship by ID (assuming you've mapped the FK to a property in the model) instead of setting the entity itself.
If your IDs are not store generated, make sure you set them too. If they are, make sure they are defined as store generated in EDMX.
If you have FKs in the DB, make sure the EDMX is aware of them so that the inserts will happen in the correct order (or if using Oracle you can try using deferred constraints instead if you want).
I have an entity/class/table which is referenced from several other entities, and I use Fluent NHibernate to handle the ORM for me. In a few instances, it's a simple reference where I can store the foreign key ID as a column and handle the reference in that simple way, but in a few other instances I need to reference a list of these items, and it needs done for at least three classes I can think of. You can assume this setup will be copied to handle the other classes' relationships.
Here's how the common entity looks (the one that is owned by several other entities in HasManys):
public class Student {
public virtual int Id {get; set;}
public virtual string Name {get; set;}
}
And, here's what the ShopCourse entity looks like:
public class ShopCourse {
public virtual int Id {get; set;}
public virtual int Name {get; set;}
public virtual IList<Student> Students {get; set;}
}
Imagine that a couple other classes I have, such as specific courses, can "own" several students. In order to maintain that relationship I must create a table in my database that tracks the foreign keys between the two (for each entity that references Student) - no entity needed for this intermediate table, and Fluent won't need to think of it unless I hand it the string name of the table itself:
Table: ShopCourseStudents
int - ShopCourseId
int - StudentId
Lastly, here are my mappings. You can assume that the entities themselves map out fine - things such as the naming scheme for the Id are resolved and working correctly. The issue lies when I attempt to initialize any entity that has a HasMany of Student:
//Inside a FluentlyConfigure().Mappings(m => m.AutoMappings.Add() call:
.Override<ShopCourse>(map => {
map.HasMany(x => x.Students)
.Table("ShopCourseStudents")
.KeyColumns.Add("ShopCourseId")
.KeyColumns.Add("StudentId")
.Cascade.All();
})
The issue is that when I attempt to load a list of ShopCourses I get the Fluent error:
Foreign key (ABC123AF9:Student [ShopCourseId, StudentId]) must have
same number of columns as the referenced primary key (ShopCourses
[Id])
I do not override Fluent's mapping of Student as it's straightforward. For the purpose of this example, Student doesn't need to know which ShopCourses it belongs to, or any of the other courses that may own that particular Student record.
This seems like I'm doing something basic, wrong - what is it, exactly? Much obliged in advance!
So, the issue was with the custom code that I re-use with my projects, apparently the piece written to handle the ManyToMany convention is mostly broken. What I was looking for here was a ManyToMany relationship, not HasMany. The issue I had was that my code was forcing a reference on the child object (in this example, Student) to the parent, which I do not need and only complicates things. Removing that, and my ManyToMany then works:
.Override<ShopCourse>(map => {
map.HasManyToMany(x => x.Students)
.Table("ShopCourseStudents")
.ParentKeyColumn("ShopCourseId")
.ChildKeyColumn("StudentId")
.Cascade.All()
I'm using EF 4.3 with CodeFirst and I have a supertype/subtype model similar to this one:
public Person {
long Id { get; set; }
[Required]
string Name { get; set; }
}
public Costumer : Person {
string SomeData { get; set; }
[Required]
string SomeRequiredData { get; set; }
}
This is a simplified version of the problem. The tables have several fields.
A person can be a "promoted" to Costumer later in the application. So Person is created first and then transformed to Customer.
The question is: After I create Person how can I "promote" it to Vendor without creating (or recreating) a new Person record?
If I do:
var costumer = new Costumer {
Id = [same id used before when Person was created],
SomeRequiredData = "Data"
};
The model gives an error saying that Name is required. I should not be required to repeat all required info from Person in the new Vendor instance since it's already there in the original Person record.
Can anybody help?
P.S. The model is configured to create 2 separate tables, one for each class...
After I create Person how can I "promote" it to Vendor without
creating (or recreating) a new Person record?
You most can't with EF because you cannot change the type of existing instance (you cannot cast Person to Customer). Because you cannot cast the entity you also cannot update its type in the database with EF. In the same time you cannot insert Customer instance with existing Id because this operation expect inserting both parent and child type (remember Customer is a Person in your model and because of that inserting Customer means inserting Person as well).
The reason why it doesn't work is that your domain model is wrong. You should not have Customer as subtype of the Person because in OOP it means exactly what happened now - you cannot change person to customer without creating a new person instance (because customer is a person). To support this scenario you must have only Person entity and this entity must have property describing its type.