Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I use Entity Framework Version 6 or 7 to update an object and its children automatically?

Tags:

I have three tables. Word -> WordForm -> SampleSentence. Each Word has different WordForms and then each form can have one or more SampleSentence

CREATE TABLE [dbo].[Word] (     [WordId]       VARCHAR (20) NOT NULL,     [CategoryId]   INT          DEFAULT ((1)) NOT NULL,     [GroupId]      INT          DEFAULT ((1)) NOT NULL,     PRIMARY KEY CLUSTERED ([WordId] ASC),     CONSTRAINT [FK_WordWordCategory] FOREIGN KEY ([CategoryId]) REFERENCES [dbo].[WordCategory] ([WordCategoryId]),     CONSTRAINT [FK_WordWordGroup] FOREIGN KEY ([GroupId]) REFERENCES [dbo].[WordGroup] ([WordGroupId]) );  CREATE TABLE [dbo].[WordForm] (     [WordFormId]   VARCHAR (20)  NOT NULL,     [WordId]       VARCHAR (20)  NOT NULL,     [Primary]      BIT           DEFAULT ((0)) NOT NULL,     [PosId]        INT           NOT NULL,     [Definition]   VARCHAR (MAX) NULL,     PRIMARY KEY CLUSTERED ([WordFormId] ASC),     CONSTRAINT [FK_WordFormPos] FOREIGN KEY ([PosId]) REFERENCES [dbo].[Pos] ([PosId]),     CONSTRAINT [FK_WordFormWord] FOREIGN KEY ([WordId]) REFERENCES [dbo].[Word] ([WordId]) );   CREATE TABLE [dbo].[SampleSentence] (     [SampleSentenceId] INT           IDENTITY (1, 1) NOT NULL,     [WordFormId]       VARCHAR (20)  NOT NULL,     [Text]             VARCHAR (MAX) NOT NULL,     CONSTRAINT [PK_SampleSentence] PRIMARY KEY CLUSTERED ([SampleSentenceId] ASC),     CONSTRAINT [FK_SampleSentenceWordForm] FOREIGN KEY ([WordFormId]) REFERENCES [dbo].[WordForm] ([WordFormId]) ); 

I am taking the data from these tables to a front-end client and this then modifies the data and adds or deletes WordForms and SampleSentences.

I then bring the data back to the server.

Is there some way that Entity Framework can check to see changes in the object that I bring back to the server and make changes to the database or do I have to do some form of comparison where I check the before and after of the Word, WordForm and Sample Sentence objects?

For reference here are the C# objects I'm using:

public class Word     {         public string WordId { get; set; } // WordId (Primary key) (length: 20)         public int CategoryId { get; set; } // CategoryId         public int GroupId { get; set; } // GroupId          // Reverse navigation         public virtual System.Collections.Generic.ICollection<WordForm> WordForms { get; set; } // WordForm.FK_WordFormWord          // Foreign keys         public virtual WordCategory WordCategory { get; set; } // FK_WordWordCategory         public virtual WordGroup WordGroup { get; set; } // FK_WordWordGroup          public Word()         {             CategoryId = 1;             GroupId = 1;             WordForms = new System.Collections.Generic.List<WordForm>();         }     }  public class WordForm     {         public string WordFormId { get; set; } // WordFormId (Primary key) (length: 20)         public string WordId { get; set; } // WordId (length: 20)         public bool Primary { get; set; } // Primary         public int PosId { get; set; } // PosId         public string Definition { get; set; } // Definition          // Reverse navigation         public virtual System.Collections.Generic.ICollection<SampleSentence> SampleSentences { get; set; } // SampleSentence.FK_SampleSentenceWordForm          // Foreign keys         public virtual Pos Pos { get; set; } // FK_WordFormPos         public virtual Word Word { get; set; } // FK_WordFormWord          public WordForm()         {             Primary = false;             SampleSentences = new System.Collections.Generic.List<SampleSentence>();         }     }  public class SampleSentence : AuditableTable     {         public int SampleSentenceId { get; set; } // SampleSentenceId (Primary key)         public string WordFormId { get; set; } // WordFormId (length: 20)         public string Text { get; set; } // Text          // Foreign keys         public virtual WordForm WordForm { get; set; } // FK_SampleSentenceWordForm     } 

Here is what I have been able to come up with so far but this does not include checking for the SampleSentence and I am not sure how to do that:

    public async Task<IHttpActionResult> Put([FromBody]Word word)     {         var oldObj = db.WordForms             .Where(w => w.WordId == word.WordId)             .AsNoTracking()             .ToList();         var newObj = word.WordForms.ToList();          var upd = newObj.Where(n => oldObj.Any(o =>             (o.WordFormId == n.WordFormId) && (o.PosId != n.PosId || !o.Definition.Equals(n.Definition) )))             .ToList();         var add = newObj.Where(n => oldObj.All(o => o.WordFormId != n.WordFormId))             .ToList();         var del = oldObj.Where(o => newObj.All(n => n.WordFormId != o.WordFormId))             .ToList();         foreach (var wordForm in upd)         {             db.WordForms.Attach(wordForm);             db.Entry(wordForm).State = EntityState.Modified;         }         foreach (var wordForm in add)         {             db.WordForms.Add(wordForm);         }         foreach (var wordForm in del)         {             db.WordForms.Attach(wordForm);             db.WordForms.Remove(wordForm);         }         db.Words.Attach(word);         db.Entry(word).State = EntityState.Modified;         await db.SaveChangesAsync(User, DateTime.UtcNow);         return Ok(word);     } 
like image 740
Alan2 Avatar asked Apr 27 '16 01:04

Alan2


People also ask

How do I update items in Entity Framework?

This can be achieved in several ways: setting the EntityState for the entity explicitly; using the DbContext. Update method (which is new in EF Core); using the DbContext. Attach method and then "walking the object graph" to set the state of individual properties within the graph explicitly.

What is difference between Entity Framework 5 and 6?

EF5 is built into the core of . NET 4.5, whereas EF6 has been shifted out, and is open source. This means that you must add the new EF6 assemblies to all of the relevant projects in the solution, in particular the entry project. This means that you must remove assembly System.


1 Answers

Sorry, no

The answer to your question literally (as in the title) is no. There is no way to do this automatically with Entity Framework. In what is called disconnected scenarios, properly saving changes from a client is something developers should take care of themselves.

As mentioned, EF used to have self-tracking entities, but soon this approach was deprecated although in official documentation it was never clearly explained why. Probably because "STEs made (change tracking) easier, but at the cost of making almost everything else really hard.." It fitted perfectly in the ObjectContext API with database-first-generated class models with t4 templates, but, as we all know, the DbContext API and code-first have become EF's recommended (and soon the only supported) architecture. Of course, with code-first, EF can't enforce any STE implementation.

Or...?

It's somewhat frustrating that EF never filled this gap later, for instance by supplying an API similar to what GraphDiff offers (or maybe by now I should say offered). There are two reasonable alternatives that I'm aware of.

Entity Framework's proposition

Lerman and Miller, in their book Programming Entity Framework: DbContext, proposed an alternative technique that was the closest to a substitute of self-tracking entities the EF team has come up with so far. It revolves around this interface:

public interface IObjectWithState {     State State { get; set; }     Dictionary<string, object> OriginalValues { get; set; } } 

Where State is

public enum State {     Added,     Unchanged,     Modified,     Deleted } 

For this approach to work properly, each entity should implement the interface. Further, each DbContext subclass needs a number of methods. A method to populate the OriginalValues property when an entity is materialized and methods to synchronize its change tracker with changes recorded in entities when they are returned to a context. It's too much to copy all this code here, you can find it in the book, starting at page 102.

Well, if you implement all that, you have self-tracking entities of sorts. It's quite elaborate, although once implemented, it'll "just work". However, a major drawback is that all consumers of your context must set this State property when an entity is added or deleted. That's a daunting responsibility to impose on client code!

Breeze

Breeze offers a complete solution that goes all the way from the DAL in your service to javascript code in the client. That's both incredibly convenient and incredibly scary.

In javascript you get a LINQ-like syntax:

var query = breeze.EntityQuery            .from("Customers")            .where("CompanyName", "startsWith", "A")            .orderBy("CompanyName"); 

This communicates with Breeze's EntityManager in C# code:

var manager = new Breeze.Sharp.EntityManager(serviceName); var results = await manager.ExecuteQuery(query); 

This EntityManager basically is a wrapper around an EF context. If all the moving parts have been set up properly, it virtually brings the EF context into your javascript, with change tracking, saving changes and all. I work with it in one project and really, it's very convenient.

But if you use Breeze, it's Breeze all the way. It affects everything. A change in the database schema requires changes in javascript. That's scary, but something you can get used to. But if you want to do things your own way, it becomes very hard (though not impossible) to bend Breeze to your needs. Like living with your mother in law. I think in many cases, in the end a combination of Breeze and other patterns becomes inevitable.

But should you want it anyway?

Generally speaking, a major drawback of any automated tracking of disconnected entities is that it makes it far too easy to use original entity objects for data transfer. The thing is, in most cases, full entities contain far more data than the client requires (or is allowed to see). Using dedicated slim DTOs can improve performance dramatically. And of course they act as an abstraction layer between DAL and UI/controllers.

Yes, with DTOs we always have to "repaint the state" server-side. So be it. It really is the recommended approach for disconnected scenarios.

John Papa, in his PluralSight course on the hot-towel template for SPAs, when explaining Breeze, recognizes this problem. He proposes a solution with "partial entities". It is a solution, but quite elaborate and clunky. And of course, still the entities are at the base of the data transfer.

like image 160
Gert Arnold Avatar answered Oct 24 '22 04:10

Gert Arnold