【问题标题】:most efficient way to process a variable size dataset处理可变大小数据集的最有效方法
【发布时间】:2018-02-08 06:49:13
【问题描述】:

我正在寻找有关处理可变大小数据集的最有效方法的建议。我有一个用户要求,即提供一个 Web 界面,使用户能够上传一个包含记录 ID 列表的 Excel 工作表,这些字段要更新和新值,每行可以是不同的字段和不同的值,行数可以从几十到大约 20,000 不等。目标表在 Microsoft SQL 数据库中

我使用的技术堆栈是 C#、使用 WCF 的 MVC 到自定义 ESB、MSMQ、实体框架(但我无法更改表结构以启用乐观并发)和 MS SQL。

所以解析数据源很好,但我不确定从那里开始的最佳方法。 我最好为每一行创建单独的消息还是应该解析结果集并在可能的情况下将消息分组(即字段名称和值匹配的位置)到单个更大的更新语句中并将其作为消息传递

我是直接通过实体框架更新还是使用存储过程更好?

【问题讨论】:

    标签: c# asp.net-mvc entity-framework wcf msmq


    【解决方案1】:

    这是一个基于名称/值对列表更新 EF 实体的小助手方法;

    public void Update<T>(T entity, Dictionary<string, string> valuesToUpdate) where T : class
    {
        var entry = ChangeTracker.Entries<T>().Where(e => object.ReferenceEquals(e.Entity, entity)).Single();
        foreach (var name in valuesToUpdate.Keys)
        {
            var pi = typeof(T).GetProperty(name);
            pi.SetValue(entity, Convert.ChangeType(valuesToUpdate[pi.Name], pi.PropertyType));
            entry.Property(pi.Name).IsModified = true;
        }
    }
    

    以及如何使用它的完整示例:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity;
    using System.Linq;
    
    namespace Ef6Test
    {
        public class Car
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public int Color { get; set; }
            public DateTime UpdateDate { get; set; }
    
        }
    
        class Db : DbContext
        {
    
            public void Update<T>(T entity, Dictionary<string, string> valuesToUpdate) where T : class
            {
                var entry = ChangeTracker.Entries<T>().Where(e => object.ReferenceEquals(e.Entity, entity)).Single();
                foreach (var name in valuesToUpdate.Keys)
                {
                    var pi = typeof(T).GetProperty(name);
                    pi.SetValue(entity, Convert.ChangeType(valuesToUpdate[pi.Name], pi.PropertyType));
                    entry.Property(pi.Name).IsModified = true;
                }
            }
    
            public DbSet<Car> Cars { get; set; }
    
    
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
    
            }
    
    
    
            class Program
            {
    
    
                static void Main(string[] args)
                {
    
                    Database.SetInitializer(new DropCreateDatabaseAlways<Db>());
    
                    using (var db = new Db())
                    {
                        db.Database.Log = m => Console.WriteLine(m);
                        db.Database.Initialize(true);
                    }
                    int id;
                    using (var db = new Db())
                    {
                        db.Database.Log = m => Console.WriteLine(m);
    
                        var c = db.Cars.Create();
                        c.Color = 2;
                        c.UpdateDate = DateTime.Now;
    
                        db.Cars.Add(c);
    
                        db.SaveChanges();
                        id = c.Id;
    
                    }
    
                    using (var db = new Db())
                    {
                        db.Database.Log = m => Console.WriteLine(m);
    
                        var c = new Car() { Id = id };
                        var updates = new Dictionary<string, string>();
                        updates.Add(nameof(Car.Color), "3");
                        updates.Add(nameof(Car.UpdateDate), "2017-01-02");
                        db.Cars.Attach(c);
    
                        db.Update(c, updates);
                        db.SaveChanges();
    
                    }
    
                    Console.WriteLine("Hit any key to exit");
                    Console.ReadKey();
                }
            }
        }
    }
    

    这是 UPDATE EF 生成的:

    UPDATE [dbo].[Cars]
    SET [Color] = @0, [UpdateDate] = @1
    WHERE ([Id] = @2)
    
    -- @0: '3' (Type = Int32)
    
    -- @1: '1/2/2017 12:00:00 AM' (Type = DateTime2)
    
    -- @2: '1' (Type = Int32)
    

    注意只修改改变的属性,不修改Name。

    【讨论】:

    • 太棒了,感谢您的建议,我会尝试实施它们并告诉您进展如何
    【解决方案2】:

    我一直想追求类型安全。因此,我将创建一个类来显示您的值,并使用一个通用适配器类来处理数据库值的获取和更新。

    你的显示类需要这样的东西:

    abstract class DisplayedValue
    {
        public int Id {get; protected set;}
        public string FieldDescription {get; protected set;}
        public abstract string Value {get; set;}
    }
    

    如果您尝试将整数值分配给 DateTime 或其他无效转换,我们希望编译器发出投诉。所以我们需要一个通用类来保存获取的值,并将显示的值转换为获取的值

    class Display<Tproperty> : Display
    {
        public override string Value
        {
            get {return this.FetchValue.ToString();}
            set {this.SetValue(Parse(value));}
        }
    
        public Func<string, TProperty> Parse {get; set;}
    
        public Func<int, TProperty> FetchValue {get; set;}
        public Action <int, TProperty> SetValue {get; set;}
    }
    

    这个类代表你想要显示的属性的原始值。因为我不知道您要在行中显示的项目类型(简单数字?Guids?客户名称?),所以我需要一个 Parse 函数,将要更新的字符串解析为要更新的值。

    TODO:如果 ToString() 不适合将您的属性转换为显示值,请考虑使用将您的 TProperty 转换为 DisplayValue 的 Func 属性:

    public Func<TProperty, string> ToDisplayValue {get; set;}
    

    TODO:为提高性能,请考虑跟踪数据是否已被提取和翻译,如果需要,不要再次提取/翻译。

    FetchValue 是一个函数,它接受一个 int Id,并返回必须显示的项目的 Tproperty 值。

    UpdateValue 是一个 void 函数,它将一个 Id 和一个要更新的 Tproperty 值作为输入。它会更新正确的值

    所以要创建一个你需要的 Display 对象:

    • 要显示的 ID
    • 字段说明
    • 将显示的值解析为 TProperty 值的解析函数
    • 获取数据的函数
    • 用于更新数据的 void 函数

    你注意到了吗,在这个课程中我从未提到我使用数据库来获取或更新数据。这隐藏在获取和更新数据的委托函数中。这允许重用以将数据存储在其他媒体中,例如变量、流、文件等

    例如:一个带有学生的 SchoolDbContext:

    class Student
    {
        public int Id {get; set;}             // primary Key
        public DateTime Birthday {get; set;
        public string FirstName {get; set;}
        ...                                   // other properties
    }
    class SchoolDbContext : DbContext
    {
        public DbSet<Student> Students {get; set;} // the table you want to update
        ...                                        // other tables
    }
    

    假设您想要显示一行,该行可以更新 ID 为 myStudentId 的学生的生日。

    int myStudentId = ...
    MyDbContext myDbContext = ...
    DisplayedValue birthday = new Display<DateTime>()
    {
        Id = myStudentId,
        FieldDescription = "Birthday",
    
        // Parse function to parse the update string to a DateTime
        Parse = (txt) => DateTime.Parse(txt),
    
        // function to parse the DateTime to a displayable string
        ToDisplayValue = (birthday) => birthDay.ToString("yyyy/MMM/DD"),
    
        // the function that fetches the Birthday of Student with Id from myDbContext:
        FetchValue = (id) => myDbContext.Students
            .Where(student => student.Id == id)
            .Select(student => student.Birthday)
            .SingleOrDefault();
    
        // the function that updates the Birthday of the Student with Id from myDbContext:
        UpdateValue = (id, valueToUpdate) =>
        {
             Student studentToUpdate = dbContext.Students
                 .Where(student => student.Id == id)
                 .SingleOrDefault();
             studentToUpdate.BirthDay = valueToUpdate);
             myDbContext.SaveChanges();            
        },
    }
    

    虽然这是一个非常简洁且可重复使用的解决方案,但对于您要显示的每个项目来说,工作量都很大。如果你想在工厂中实现自动化,你会遇到几个问题

    • 您需要确保每个项目都需要有一个 Id
    • 如何获取显示项目的描述性名称?属性名够吗?

    .

    interface IId
    {
        int Id {get;}
    }
    

    您需要确保 DbContext 中将成为 DbSet 的每个类都派生自此接口。

    public DisplayFactory
    {
        public MyDbContext MyDbContext {get; set;}
    
        public Display<TProperty> Create<TEntity, TProperty>(int id,
           Expression<Func<TEntity, TProperty>> propertySelector,
           Action<TEntity, TProperty> propertyUpdater,
           Func<string, TProperty> parse,
           Func<TProperty, string> toDisplayValue)
        {
            return new Display<TProperty>()
            {
                Id = id,
                Parse = parse,
                ToDisplayValue = toDisplayValue,
    
                FetchValue = (id) => this.MyDbContext.DbSet<TEntity>()
                     .Where(entity => entity.Id == id) // this is where I need the interface
                     .Select(propertySelector)
                     .SingleOrDefault(),
    
                SetValue = (id, valueToUpdate) =>
                {
                     TEntity entityToUpdate = this.MyDbContext.DbSet<TEntity>()
                         .Where(entity => entity.Id == id)
                         .SingleOrDefault();
                     propertyUpdate(entityToUpdate, valueToUpdate);
                     SaveChanges(); 
                }
            }
        }
    

    用法:

    DisplayFactory factory = new DisplayFactory()
    {
        MyDbContext = ...
    }
    
    DisplayedValue createdValue = factory.Create(id,
       student => student.Birthday,                   // property selector
       (student, value) => student.Birthday = value;  // property updater
       (txt) => DateTime.Parse(txt),                  // string to Datetime
       (birthday) => birthDay.ToString(...));          // to displayed birthday
    

    注意,这是完全类型安全的,如果你想更新不存在的列或不存在的类型,或者想要分配不兼容的类型,例如将 int 分配给 DateTime,编译器不会接受它。您不能不小心更新刚刚显示的其他属性。

    如果你还是觉得这工作量太大,可以考虑使用反射和PropertyInfo来选择DbSet和你要更新的Column。

    但是请记住,您仍然必须提供解析器来显示并将显示的字符串值解析为要更新的值。如果您使用不存在的表或列的名称,您将失去所有类型安全性,编译器将接受它。

    我不确定额外的测试时间是否相当于节省的打字时间。

    【讨论】:

    • 太棒了,感谢您的建议,我会尝试实施它们并告诉您进展如何
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-04
    • 2014-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-30
    • 2020-09-27
    相关资源
    最近更新 更多