草庐IT

c# - 在 Entity Framework 5 Code First 中覆盖 SaveChanges 以复制旧遗留库的行为

coder 2024-05-31 原文

我们公司发布了一套各种操作数据库中数据的应用程序。每个应用程序都有其特定的业务逻辑,但所有应用程序共享一个公共(public)的业务规则子集。常见的东西被封装在一堆用 C++ 编写的遗留 COM DLL 中,它们使用“经典 ADO”(它们通常调用存储过程,有时它们使用动态 SQL)。这些 DLL 中的大多数都有基于 XML 的方法(更不用说基于专有格式的方法!)来创建、编辑、删除和检索对象,还有额外的操作,例如快速复制和转换许多实体的方法。

中间件 DLL 现在很旧,我们的应用程序开发人员想要一个新的面向对象(而不是面向 xml)的中间件,它可以被 C# 应用程序轻松使用。
公司里的很多人说我们应该忘记旧的范式,转向新的很酷的东西,比如 Entity Framework 。他们对 POCO 的简单性很感兴趣,他们希望使用 LINQ 来检索数据(DLL 的基于 Xml 的查询方法不是那么容易使用,永远不会像 LINQ 那样灵活)。

所以我试图为一个简化的场景创建一个模型(真实的场景要复杂得多,在这里我将只发布简化场景的一个简化子集!)。我使用的是 Visual Studio 2010、Entity Framework 5 Code First、SQL Server 2008 R2。
如果我犯了愚蠢的错误,请怜悯,我是 Entity Framework 的新手。
因为我有很多不同的疑问,我会在不同的线程中发布它们。
这是第一个。传统 XML 方法具有如下签名:

    bool Edit(string xmlstring, out string errorMessage)

使用这样的格式:
<ORDER>
    <ID>234</ID>
    <NAME>SuperFastCar</NAME>
    <QUANTITY>3</QUANTITY>
    <LABEL>abc</LABEL>
</ORDER>

Edit 方法实现了以下业务逻辑:当 Quantity 更改时,必须对具有相同标签的所有订单应用“自动缩放”。
例如。有三个订单:订单A的数量= 3,标签= X。订单B的数量= 4,标签= X。订单C的数量= 5,标签= Y。
我调用 Edit 方法为 OrderA 提供一个新的数量 = 6,即我将 OrderA 的数量加倍。那么,按照业务逻辑,OrderB 的数量一定要自动翻倍,必须变成 8 个,因为 OrderB 和 OrderA 的标签相同。 OrderC 不得更改,因为它具有不同的标签。

如何使用 POCO 类和 Entity Framework 复制它?这是一个问题,因为旧的 Edit 方法一次只能更改一个订单,而
当调用 SaveChanges 时, Entity Framework 可以更改很多订单。此外,对 SaveChanges 的单个调用也可以创建新订单。
临时假设,仅用于本次测试: 1) 如果同时更改多个订单数量,并且所有订单数量的比例因子不相同,则不会发生比例缩放; 2) 新添加的订单即使与缩放订单的标签相同,也不会自动缩放。

我试图通过覆盖 SaveChanges 来实现它。

POCO 类:
using System;

namespace MockOrders
{
    public class Order
    {
        public Int64 Id { get; set; }
        public string Name { get; set; }
        public string Label { get; set; }
        public decimal Quantity { get; set; }
    }
}

迁移文件(创建索引):
namespace MockOrders.Migrations
{
    using System;
    using System.Data.Entity.Migrations;

    public partial class UniqueIndexes : DbMigration
    {
        public override void Up()
        {
            CreateIndex("dbo.Orders", "Name", true /* unique */, "myIndex1_Order_Name_Unique");
            CreateIndex("dbo.Orders", "Label", false /* NOT unique */, "myIndex2_Order_Label");
        }

        public override void Down()
        {
            DropIndex("dbo.Orders", "myIndex2_Order_Label");
            DropIndex("dbo.Orders", "myIndex1_Order_Name_Unique");
        }
    }
}

数据库上下文:
using System;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.Linq;

namespace MockOrders
{
    public class MyContext : DbContext
    {
        public MyContext() : base(GenerateConnection())
        {
        }

        private static string GenerateConnection()
        {
            var sqlBuilder = new System.Data.SqlClient.SqlConnectionStringBuilder();
            sqlBuilder.DataSource = @"localhost\aaaaaa";
            sqlBuilder.InitialCatalog = "aaaaaa";
            sqlBuilder.UserID = "aaaaa";
            sqlBuilder.Password = "aaaaaaaaa!";
            return sqlBuilder.ToString();
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new OrderConfig());
        }

        public override int SaveChanges()
        {
            ChangeTracker.DetectChanges();

            var groupByLabel = from changedEntity in ChangeTracker.Entries<Order>() 
                        where changedEntity.State == System.Data.EntityState.Modified
                                && changedEntity.Property(o => o.Quantity).IsModified
                                && changedEntity.Property(o => o.Quantity).OriginalValue != 0
                                && !String.IsNullOrEmpty(changedEntity.Property(o => o.Label).CurrentValue)
                        group changedEntity by changedEntity.Property(o => o.Label).CurrentValue into x
                        select new { Label = x.Key, List = x};

            foreach (var labeledGroup in groupByLabel)
            {
                var withScalingFactor = from changedEntity in labeledGroup.List 
                    select new 
                    { 
                        ChangedEntity = changedEntity, 
                        ScalingFactor = changedEntity.Property(o => o.Quantity).CurrentValue / changedEntity.Property(o => o.Quantity).OriginalValue 
                    };

                var groupByScalingFactor = from t in withScalingFactor 
                                           group t by t.ScalingFactor into g select g;

                // if there are too many scaling factors for this label, skip automatic scaling
                if (groupByScalingFactor.Count() == 1)
                {
                    decimal scalingFactor = groupByScalingFactor.First().Key;
                    if (scalingFactor != 1)
                    {
                        var query = from oo in this.AllTheOrders where oo.Label == labeledGroup.Label select oo;

                        foreach (Order ord in query)
                        {
                            if (this.Entry(ord).State != System.Data.EntityState.Modified
                                && this.Entry(ord).State != System.Data.EntityState.Added)
                            {
                                ord.Quantity = ord.Quantity * scalingFactor;
                            }
                        }
                    }
                }

            }

            return base.SaveChanges();

        }

        public DbSet<Order> AllTheOrders { get; set; }
    }

    class OrderConfig : EntityTypeConfiguration<Order>
    {
        public OrderConfig()
        {
            Property(o => o.Name).HasMaxLength(200).IsRequired();
            Property(o => o.Label).HasMaxLength(400);
        }
    }
}

它似乎有效(当然,排除错误),但这是一个只有 1 个类的示例:一个真正的生产应用程序可能有数百个类!
恐怕在实际场景中,由于存在大量约束和业务逻辑,SaveChanges 的覆盖可能会很​​快变得冗长、困惑且容易出错。
一些同事也关心性能。在我们遗留的 DLL 中,很多业务逻辑(例如“自动”操作)存在于存储过程中,一些同事担心基于 SaveChanges 的方法可能会引入过多的往返并影响性能。
在 SaveChanges 的覆盖中,我们也可以调用存储过程,但是事务完整性呢?如果我对数据库进行更改怎么办
在我调用“base.SaveChanges()”和“base.SaveChanges()”失败之前?

有不同的方法吗?我错过了什么吗?

非常感谢!

德米特里奥

附言顺便说一下,覆盖 SaveChanges 和注册到“SavingChanges”事件之间有区别吗?我阅读了这个文件,但它没有解释是否有区别:
http://msdn.microsoft.com/en-us/library/cc716714(v=vs.100).aspx

这个帖子:
Entity Framework SaveChanges - Customize Behavior?

说“当覆盖 SaveChanges 时,您可以在调用 base.SaveChanges 之前和之后放置自定义逻辑”。但是还有其他注意事项/优点/缺点吗?

最佳答案

我想说这个逻辑属于您的 MockOrders.Order 类,属于使用您的 Order 类的更高层的类(例如 BusinessLogic.Order)或属于 Label 类。听起来您的标签充当连接属性,因此,在不知道细节的情况下,我会说将其拉出并使其成为自己的实体,这将为您提供导航属性,以便您可以更自然地访问具有相同标签的所有订单.

如果修改数据库以规范化标签不是一个好办法,请为此目的构建一个 View 并将其引入您的实体模型。

关于c# - 在 Entity Framework 5 Code First 中覆盖 SaveChanges 以复制旧遗留库的行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14932715/

有关c# - 在 Entity Framework 5 Code First 中覆盖 SaveChanges 以复制旧遗留库的行为的更多相关文章

  1. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  2. ruby - 无法覆盖 irb 中的 to_s - 2

    我在pry中定义了一个函数:to_s,但我无法调用它。这个方法去哪里了,怎么调用?pry(main)>defto_spry(main)*'hello'pry(main)*endpry(main)>to_s=>"main"我的ruby版本是2.1.2看了一些答案和搜索后,我认为我得到了正确的答案:这个方法用在什么地方?在irb或pry中定义方法时,会转到Object.instance_methods[1]pry(main)>defto_s[1]pry(main)*'hello'[1]pry(main)*end=>:to_s[2]pry(main)>defhello[2]pry(main)

  3. ruby - 覆盖相似的方法,更短的语法 - 2

    在Ruby类中,我重写了三个方法,并且在每个方法中,我基本上做同样的事情:classExampleClassdefconfirmation_required?is_allowed&&superenddefpostpone_email_change?is_allowed&&superenddefreconfirmation_required?is_allowed&&superendend有更简洁的语法吗?如何缩短代码? 最佳答案 如何使用别名?classExampleClassdefconfirmation_required?is_a

  4. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  5. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  6. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  7. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  8. Ruby - 如何处理子类意外覆盖父类(super class)私有(private)字段的问题? - 2

    假设您编写了一个类Sup,我决定将其扩展为SubSup。我不仅需要了解你发布的接口(interface),还需要了解你的私有(private)字段。见证这次失败:classSupdefinitialize@privateField="fromsup"enddefgetXreturn@privateFieldendendclassSub问题是,解决这个问题的正确方法是什么?看起来子类应该能够使用它想要的任何字段而不会弄乱父类(superclass)。编辑:equivalentexampleinJava返回"fromSup",这也是它应该产生的答案。 最佳答案

  9. ruby - 解释为局部变量会覆盖方法名称吗? - 2

    如thisquestion,当在其自己的赋值中使用未定义的局部变量时,它的计算结果为nil。x=x#=>nil但是当局部变量的名称与现有的方法名称冲突时,就比较棘手了。为什么下面的最后一个示例返回nil?{}.instance_eval{a=keys}#=>[]{}.instance_eval{keys=self.keys}#=>[]{}.instance_eval{keys=keys}#=>nil 最佳答案 在Ruby中,因为可以在没有显式接收器和括号的情况下调用方法,所以在局部变量引用和无接收器无参数方法调用之间存在语法歧义:f

  10. ruby-on-rails - capybara poltergeist - 覆盖用户代理 - 2

    有人知道如何将capybarapoltergeist的用户代理覆盖到移动用户代理以进行测试吗?我发现了一些有关为seleniumwebdriver配置它的信息:http://blog.plataformatec.com.br/2011/03/configuring-user-agents-with-capybara-selenium-webdriver/这在capybara闹鬼中怎么可能? 最佳答案 请参阅poltergeistgithub页面上的链接:https://github.com/teampoltergeist/polte

随机推荐