我可以使用Entity Framework Version 6或7自动更新对象及其子对象吗?

Ala*_*an2 22 c# asp.net entity-framework

我有三张桌子.单词 - > WordForm - > SampleSentence.每个Word都有不同WordForms,然后每个表格可以有一个或多个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])
);
Run Code Online (Sandbox Code Playgroud)

我将这些表中的数据传输到前端客户端,然后修改数据并添加或删除WordForms和SampleSentences.

然后我将数据带回服务器.

是否存在某种方式,实体框架可以检查以查看我带回服务器并对数据库进行更改的对象中的更改,或者我必须进行某种形式的比较,其中我检查Word,WordForm之前和之后和Sample Sentence对象?

这里参考我正在使用的C#对象:

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
    }
Run Code Online (Sandbox Code Playgroud)

这是我迄今为止能够提出的,但这不包括检查SampleSentence,我不知道如何做到这一点:

    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);
    }
Run Code Online (Sandbox Code Playgroud)

Ger*_*old 18

抱歉,没有

字面上的问题(如标题中)的答案是否定的.使用Entity Framework 无法自动执行此操作.在所谓的断开连接的场景中,正确保存客户端的更改是开发人员应该自己处理的事情.

如上所述,EF曾经有自我跟踪实体,但很快这种方法已被弃用,尽管在官方文档中它从未明确解释过原因.可能是因为" STEs制作(改变跟踪)更容易,但代价是几乎所有其他东西都很难." 它完全适合ObjectContextAPI,数据库优先生成的类模型和t4模板,但是,众所周知,DbContextAPI和代码优先已成为EF的推荐(并且很快是唯一支持的)架构.当然,对于代码优先,EF不能强制执行任何STE实现.

要么...?

有点令人沮丧的是EF以后从未填补这个空白,例如提供类似于GraphDiff提供的API (或者现在我应该说提供).我知道有两种合理的替代方案.

实体框架的主张

Lerman和Miller在他们的书" Programming Entity Framework:DbContext"中提出了一种替代技术,该技术最接近EF团队迄今为止提出的自我跟踪实体的替代品.它围绕这个界面:

public interface IObjectWithState
{
    State State { get; set; }
    Dictionary<string, object> OriginalValues { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

哪里State

public enum State
{
    Added,
    Unchanged,
    Modified,
    Deleted
}
Run Code Online (Sandbox Code Playgroud)

为使此方法正常工作,每个实体都应实现该接口.此外,每个DbContext子类都需要许多方法.OriginalValues在实体实现时填充属性的方法,以及在将实体返回到上下文时将其更改跟踪器与实体中记录的更改同步的方法.这里复制所有这些代码太多了,你可以在书中找到它,从第102页开始.

好吧,如果你实现了所有这些,你就有各种各样的自我跟踪实体.它非常复杂,虽然一旦实施,它"只是工作".但是,主要缺点是上下文的所有使用者必须State在添加或删除实体时设置此属性.强加于客户端代码是一项艰巨的任务!

微风

Breeze提供了一个完整的解决方案,从服务中的DAL到客户端中的javascript代码.这既令人难以置信又非常可怕.

在javascript中,您将获得类似LINQ的语法:

var query = breeze.EntityQuery
           .from("Customers")
           .where("CompanyName", "startsWith", "A")
           .orderBy("CompanyName");
Run Code Online (Sandbox Code Playgroud)

这与Creeze EntityManager代码中的Breeze通信:

var manager = new Breeze.Sharp.EntityManager(serviceName);
var results = await manager.ExecuteQuery(query);
Run Code Online (Sandbox Code Playgroud)

EntityManager基本上是围绕EF上下文的包装器.如果所有移动部件都已正确设置,它实际上会将EF上下文带入您的javascript,并进行更改跟踪,保存更改等等.我在一个项目中使用它,真的,它非常方便.

但是如果你使用Breeze,它一直都是Breeze.它会影响一切.数据库架构的更改需要更改javascript.那可怕,但你可以习惯的东西.但是如果你想以自己的方式做事,那么将Breeze根据你的需求变得非常困难(尽管不是不可能).喜欢和你的婆婆住在一起.我认为在很多情况下,最终Breeze和其他模式的结合变得不可避免.

但是你还想要吗?

一般而言,任何自动跟踪断开连接的实体的主要缺点是它使得使用原始实体对象进行数据传输变得非常容易.事实上,在大多数情况下,完整实体包含的数据远远多于客户端要求(或允许查看)的数据.使用专用的纤薄DTO可以显着提高性能.当然,它们充当DAL和UI /控制器之间的抽象层.

是的,对于DTO,我们总是必须"重新绘制状态"服务器端.就这样吧.它确实是断开连接方案的推荐方法.

在解释Breeze时,John Papa在他的关于SPA的热毛巾模板的PluralSight课程中认识到了这个问题.他提出了一个"部分实体"的解决方案.这一个解决方案,但相当复杂和笨重.当然,实体仍然是数据传输的基础.

  • _...根据您的需要弯曲Breeze变得非常困难(尽管不是不可能).就像和你母亲一起生活一样._**Bravo先生.** (5认同)

CSh*_*ark 7

正如之前的一张海报中所提到的,这在EF中并不是本地支持的,并且是最受欢迎的功能之一.

但是,如果您愿意使用GraphDiff库(或者自己编写类似的东西),则有可能.我强烈建议您使用开源GraphDiff库来更新EntityFramework中的断开连接的图形.

请查看Brent McKendrick 撰写的这篇文章,了解如何执行此操作.

此项目的github存储库也可以在此处找到

从上面的文章中,更新相关实体的图表就像下面的例子一样简单:

using (var context = new TestDbContext())  
{
    // Update the company and state that the company 'owns' the collection contacts, with contacts having associated advertisement options.
    context.UpdateGraph(company, map => map
        .OwnedCollection(p => p.Contacts, with => with
            .AssociatedCollection(p => p.AdvertisementOptions))
        .OwnedCollection(p => p.Addresses)
    );

    context.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

请注意,这个项目不再维护,虽然我已经使用它(以及许多其他)没有任何严重的问题.