处理可变大小数据集的最有效方法

时间:2018-02-08 06:49:14

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

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

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

因此解析数据源很好,但我不确定从那里开始的最佳方式。 我最好为每一行创建单独的消息,或者我应该尽可能地解析结果集并将消息分组(即字段名称和值匹配)到一个更大的更新语句中并将其作为消息传递

我最好直接通过Entity Framework更新还是使用存储过程?

2 个答案:

答案 0 :(得分:0)

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

您的展示类需要这样的内容:

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显示
  • FieldDescription
  • 将显示的值解析为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和要更新的列。

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

我不确定额外的测试时间是否会影响保存的打字时间。

答案 1 :(得分:0)

这是一个基于名称/值对列表更新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 Generates:

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)

请注意,仅修改了已更改的属性,而名称不是。