Saving an EF instance which has multiple parents - c#

When mapping back from business logic objects to EF objects, one of the main problems I have is cases where the same instance has 2 parents:
(Objects are yellow, properties are orange)
In the business logic world, there is only one instance of the Tree object here (which appears as a child of multiple parents: Forest and Section)
When I map everything back into EF objects with AutoMapper, EF thinks there are 2 separate instances of tree (despite them having the same ID). It therefore creates a duplicate in the DB.
What is the correct way to manage this scenario so that both Forest and Section point to the same record of Tree in the DB?
Do we have to go through and manually make sure everything is attached which might be considered a duplicate?

Unfortunatelly EF needs to get same instance of a Tree object to consider him as same during saving whole Forest graph (overriding of his equality members doesn't help), which is not how Automapper maps object graphs by default.
But you can set up your Automapper configuration in the way it reuses existing instances during mapping:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Tree, TreeEF>().PreserveReferences();
});
Than if you have in your bussines model Forest and Section having a child reference to the same instance of a Tree, this reference will be preserved and no duplicates will be created.
EDIT
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Forest, ForestEF>().PreserveReferences();
cfg.CreateMap<Section, SectionEF>().PreserveReferences();
cfg.CreateMap<Tree, TreeEF>().PreserveReferences();
});
var mapper = config.CreateMapper();
var forest = new Forest();
var section = new Section();
var tree = new Tree();
forest.Trees.Add(tree);
forest.Sections.Add(section);
section.Trees.Add(tree);
var result = mapper.Map<Forest, ForestEF>(forest);
Console.WriteLine(object.ReferenceEquals(result.Trees[0], result.Sections[0].Trees[0]));
Console.ReadLine();
}
}
public class Forest
{
public IList<Tree> Trees { get; set; } = new List<Tree>();
public IList<Section> Sections { get; set; } = new List<Section>();
}
public class Section
{
public IList<Tree> Trees { get; set; } = new List<Tree>();
}
public class Tree
{
}
public class ForestEF
{
public IList<TreeEF> Trees { get; set; } = new List<TreeEF>();
public IList<SectionEF> Sections { get; set; } = new List<SectionEF>();
}
public class SectionEF
{
public IList<TreeEF> Trees { get; set; } = new List<TreeEF>();
}
public class TreeEF
{
}

I believe that if you don't want duplicates here, both children must not only reference the ID but also the specific instance in memory so EF knows it should be the same record (navigation property). Otherwise, you have to save the parent record first and then assign the key to each child after the fact. If this isn't a GUID but an auto generated id, then you probably need to use the same reference.

Related

Persisting Entity Framework nested/related entities

I have the following entities:
public class ModuleCriteria
{
public int ModuleCriteriaId { get; set; }
public string Criteria { get; set; }
public List<ModuleCriteriaLookup> ModuleCriteriaLookups { get; set;
}
}
public class ModuleCriteriaLookup
{
public int ModuleCriteriaLookupId { get; set; }
public int ModuleCriteriaId { get; set; } // ** (Foreign Key) **
public int SiteId { get; set; }
public string CategoryId { get; set; }
public ModuleCriteria ModuleCriteria { get; set; }
}
I have the following EF configuration in my Context class (edited for brevity):
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
base.OnModelCreating( modelBuilder );
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<ModuleCriteriaLookup>().HasRequired( mc => mc.ModuleCriteria );
// I tried adding the below line but it made no difference.
//modelBuilder.Entity<ModuleCriteria>().HasMany( mc => mc.ModuleCriteriaLookups );
}
...and I have the following DbSet properties defined in my Context class:
public DbSet<ModuleCriteria> ModuleCriteria { get; set; }
public DbSet<ModuleCriteriaLookup> ModuleCriteriaLookup { get; set; }
I have a CriteriaRepository class, which has a Save method, for persisting changes for my ModuleCriteria entities:
public void Save( ModuleCriteria moduleCriteria )
{
using ( var ctx = new MyAppContext() )
{
ctx.Entry( moduleCriteria ).State = moduleCriteria.ModuleCriteriaId == 0 ? EntityState.Added : EntityState.Modified;
ctx.SaveChanges();
}
}
A ModuleCriteria object can exist without a ModuleCriteriaLookup object, but ModuleCriteriaLookup object has to relate to an existing ModuleCriteria object (related on ModuleCriteriaId).
You can have multiple ModuleCriteraLookup objects all relating to the same one ModuleCriteria object.
The behaviour that I would like, and expect with EF, is:
1) If I create a new ModuleCriteria object (without any ModuleCriteriaLookups), call the Save method in my repository, I would expect to see new ModuleCriteria record in the db with no related ModuleCriteriaLookup records in the db.
2) If I create a new ModuleCriteria object and assign a List<ModuleCriteriaLookup> to it, call the Save method in my repository, I would expect to see new ModuleCriteria record in the db and x new ModuleCriteriaLookup rows in the db which relate to that particular ModuleCriteria.
3) If I add/edit/remove any of the ModuleCriteriaLookup objects that related to one of my ModuleCriteria objects, then call the Save method in my repository, I would expect to see any of the ModuleCriteria's deleted ModuleCriteriaLookup objects to get removed from the db, any new ones added and any edited ones simply to get updated.
So all I ever need worry about is that whatever the ModuleCriteria.ModuleCriteriaLookups property contains for a given ModuleCriteria, that's what will be reflected in the 2 tables in my DB by simply calling the Save method for the ModuleCriteria object in my repository.
Unfortunately at the moment, if I'm adding a new ModuleCriteria object with associated List<ModuleCriteriaLookup> it adds both ModuleCriteria and x ModuleCriteriaLookup rows in the db nicely. But when I want to edit or delete entries in the ModuleCriteria.ModuleCriteriaLookups property, this is not being reflected in the db. Nothing is happening with the ModuleCriteriaLookups rows.
I'm not sure where exactly the problem is, whether its whether the EF mapping configuration, or something to do with how the repository works?
The problem is located in the repository. The DbContext needs to be aware of the existence of entities. So when editing and/or deleting entities the entities need to be fetched from the database first.
This description clearly states:
The .Entry property returns objects from the context that are being
tracked by the context.
Because you directly use this properties right after creating the context, the context isn't tracking these entities and is therefore not aware that something has changed. And thereby is unable to generate the correct SQL statements.
There are several ways to deal with this, depending on the rest of your design.
One way to delete it would be:
public void DeleteModuleCriteriaLookup(ModuleCriteriaLookup[] lookups)
{
using (var ctx = new MyAppContext())
{
var moduleCriteriaId = lookups.First().ModuleCriteriaId;
var moduleCritria = (
from criteria in ctx.ModuleCriteria
where criteria.ModuleCriteriaId == moduleCriteriaId
select criteria
).Single();
var lookupIdsToDelete = lookups.Select(l => l.ModuleCriteriaLookupId);
var lookupsToDelete = (
from lookup in moduleCritria.ModuleCriteriaLookups
where lookupIdsToDelete.Contains(lookup.ModuleCriteriaLookupId)
select lookup
).ToArray();
foreach (var lookup in lookupsToDelete)
{
moduleCritria.ModuleCriteriaLookups.Remove(lookup);
}
ctx.SaveChanges();
}
}

Entity Framework 6: Adding child object to parent's list vs. setting child's navigation property to parent

I have an existing database with two tables MailServers and MailDomains in it. MailDomains has the foreign key column MailServerId pointing to the Id primary key column in MailServers. So we have a one-to-many-relationship here.
I followed this article and created my Entity Framework POCOs via the "Code first from database" model in the Entity Data Model Wizard. This produced the following two C# classes:
public partial class MailServer
{
public MailServer()
{
MailDomains = new HashSet<MailDomain>();
}
public int Id { get; set; }
public virtual ICollection<MailDomain> MailDomains { get; set; }
}
public partial class MailDomain
{
public MailDomain()
{
}
public int Id { get; set; }
public string DomainName { get; set; }
public int MailServerId { get; set; }
public virtual MailServer MailServer { get; set; }
}
Now my question is whether there is any difference between the following two approaches of creating and inserting new objects to the database.
Approach (A): Adding new child to the parent's list:
var mailServer = new MailServer();
var mailDomain = new MailDomain() {
DomainName = "foobar.net",
};
mailServer.MailDomains.Add(mailDomain);
using(var context = new MyContext){
context.MailServers.Add(mailServer);
context.SaveChanges();
}
Approach (B): Setting the child's navigation property to the parent:
var mailServer = new MailServer();
var mailDomain = new MailDomain() {
DomainName = "foobar.net",
MailServer = mailServer,
};
using(var context = new MyContext){
context.MailDomains.Add(mailDomain);
context.SaveChanges();
}
I also assume that in approach (A) the new MailDomain instance is automatically added to the collection context.MailDomains while in approach (B) the new MailServer instance is automatically added to the collection context.MailServers. Is that correct or do I have to do that manually?
So again, my question is: are the two approaches interchangeable?
It just confuses me that in the database there is only one property/column to set (namely the foreign key in MailDomains) while in the C# code there are two properties (one in each class) that could be modified.
Yes, the two approaches are interchangeable. This allows you to create and save your object graph to the database from either the perspective of the MailServer or the MailDomain.
If you do code-first, you have the option of removing the properties and mappings if they're not needed.
I also assume that in approach (A) the new MailDomain instance is
automatically added to context.MailDomains while in approach (B) the
new MailServer instance is automatically added to context.MailServers.
Is that correct or do I have to do that manually?
It depends what you mean by "added to the context". If you mean: does it automatically get saved to the database when you persist, the answer is yes. One of the big benefits to using an ORM like EF is that it handles saving a full object graph automatically (and syncing PK/FK relations, etc.).
If you mean: will the entity be available via the context before saving, I don't think so (I'm not 100% sure).

Entity Framework & Multitables

I have a linq to entity expression:
entities = new zdmEntities();
var reltables = (from r in entities.relations
orderby r.id
select new Relation
{
Id = r.id,
Devices = r.devices.device_name,
Systems = r.systems.system_name,
Models = r.models.name,
Functions = r.functions.function_name
}).ToList();
ultraGrid1.DataSource = reltables.ToList();
class Relation
{
public int Id { get; set; }
public string Devices { get; set; }
public string Systems { get; set; }
public string Models { get; set; }
public string Functions { get; set; }
}
As you can see the relation table contains a link to other tables.
The class Relation contains my columns for the datagrid.
But there is one problem... can't be posssible two way databinding between grid and database. I wrote all the updates manually but it's very difficult.
I understand that this is because in linq expression there is 'new'. But how do you make it without 'new'?
How I can display columns that I need with a two-way databinding and without own class like 'Relation'.
Windows Form. Not wpf)
Thanx, Alex.
When you write entities.Relations.Select(r => new ...) you are making a projection of each Relation EF object into a new non-EF object. By EF object I mean a class which is known by and tracked by EntityFramework.
Making changes to a EF-known class instance would propagate the changes back to DB when you save changes in your db/entity context. In contrast, making changes to a EF-unknown projection (or any projection) has no effect on the original object.
There are two ways you can achive what you want: If your DataGrid (NetAdvantage UltraGrid?) supports binding to subobjects (such as relation.device) you can then use ultraGrid.DataSource = entities.relations and define grid columns to bind to field devices.device_name. The other way would be something like this:
class Relation
{
private readonly EfRelation _originalRelation;
public Relation(EfRelation originalRelation)
{
this._originalRelation = originalRelation;
}
public string Devices
{
get { return this._originalRelation.devices.device_name; }
set { this._originalRelation.devices.device_name = value; }
}
// Repeat for other properties
}
...
var reltables = entities.relations.ToList().Select(r => new Relation(r)).ToList();
Then you just save changes to your db/object context. The EfRelation is the name of your EF Relation class, change it to the name of your EF class which represents a relation.

Deep Clone full object graph of EF hydrated POCO

Seems to be lots of info about deep cloning in C# but the object I am trying to clone is being pulled out of a database by Entity Framework. The example I have is as follows:
public class Parent
{
public int ParentID { get; set; }
public string Name { get; set; }
public virtual Person Owner { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
public int ChildID { get; set; }
public string Name { get; set; }
}
I want to make a copy of the Parent entity and all of its related objects and then save this new graph to the database. I have tried using a Serializable approach as detailed here but because of lazy loading, the child objects never get included (my example above is trivial; there are actually lots of children so to eager load all would be un-maintainable long term).
I have also tried to put a DeepClone method on each of my POCOs like so:
public Parent DeepClone()
{
Parent clone = (Parent)this.MemberwiseClone();
clone.Owner = this.Owner;
clone.Children = new Collection<Child>();
foreach (Child c in this.Children)
{
Child child = c.DeepClone();
clone.Children.Add(child);
}
return clone;
}
but clone.Children.Add(child) is throwing up an InvalidOperationException "The entity wrapper stored in the proxy does not reference the same proxy."
Can someone help me find the right solution to this. To summarize I want to be able to clone the full object graph of an EF hydrated POCO and then save all objects to the database as new data.
Thanks for any help.
UPDATE
As suggested by Ladislav Mrnka, I have gone down the DataContractSerializer route, using a ProxyDataContractResolver so that it works nicely with EF proxies. However this approach seems to serialize everything in the graph which is problematic as when saving the object back to the database I get copies of things that already exist. For example, say Parent has a ParentType: I want my Parent clone to reference the original ParentType, not for a new ParentType to be created.
So what I need is a way to stop ParentType being part of the serialization. I can mark the ParentType property as [IgnoreDataMember] but this approach could lead to properties being missed. Is there a way to configure DataContractSerializer so it only serializes the types I tell it to?

My class is creating entries in the database and I want it to stop

I'm working on a project using entity framework and code first. Here are my shortened classes
public class BenefitsForm : IAnnualForm, IAuditable
{
public BenefitsUser UserInfo { get; set; }
public CoverageLevel Medical { get; set; }
}
public class MedicalPlan : IHealthPlan
{
public int Id { get; set; }
public virtual IList<CoverageLevel> CoverageLevels { get; set; }
}
public class CoverageLevel
{
public int Id { get; set; }
public virtual MedicalPlan MedicalPlan { get; set; }
}
There are 4 coverage levels in each MedicalPlan. I already have my coverage levels and medical plans made in the database. However, when I create my BenefitsForm, it creates duplicate entries for the classes (I already have them in the database). How can I prevent it from doing this? Here is a small code snippet to show how it happens.
BenefitsForm form = new BenefitsForm() { UserInfo = new BenefitsUser() };
using(var repo = new CoverageLevelRepository())
{
form.Medical = repo.Retrieve(new NumericKey(formId))); //this retrieves the coveragelevel with the id I want.
}
formRepository.Create(form); // this creates a duplicate med plan and 4 duplicate coverage levels.
formRepository.SaveChanges();
I think because you're retrieving the Medical field from a different context than the one you're saving in -- repo vs. formRepository; formRepository isn't tracking the object created by repo, so it assumes that it's a new object, and thus creates a duplicate entry. For performance reasons, I don't believe Entity Framework will go and insert existence checks for you -- tracking is handled internally by the object context itself; each object is bound to a single object context. Try using the same repository to retrieve the MedicalPlan and write back the new BenefitsForm, and you shouldn't have duplicates.
BenefitsForm form = new BenefitsForm() { UserInfo = new BenefitsUser() };
using(var repo = new Repository())
{
form.Medical = repo.Retrieve(new NumericKey(formId))); //this retrieves the coveragelevel with the id I want.
repo.Create(form); // this creates a duplicate med plan and 4 duplicate coverage levels.
repo.SaveChanges();
}
Instead of formRepository.SaveChanges() please try with to use the SaveChanges with the SaveOptions Enum
Example:
ObjectContext.SaveChanges(
System.Data.Objects.SaveOptions.DetectChangesBeforeSave
);
Please see link for more information http://msdn.microsoft.com/en-us/library/dd395500.aspx
It's possible that using IList<...> instead of ICollection<...> for your one-to-many association is causing it to malfunction. Try changing that. Other than that, it may be code within your CoverageLevelRepository that you did not post. For more info on associations, see this guide

Categories

Resources