我正在寻找有关处理可变大小数据集的最有效方法的建议。我有一个用户要求提供一个Web界面,使用户能够上传包含记录ID列表的Excel工作表,更新和新值,每行可以是不同的字段和不同的值,行数可以从几十个到大约20,000个不等。目标表位于Microsoft SQL数据库中
我正在使用的技术堆栈是C#,MVC使用WCF到自定义ESB,MSMQ,实体框架(但我无法更改表结构以实现乐观并发)和MS SQL。
因此解析数据源很好,但我不确定从那里开始的最佳方式。 我最好为每一行创建单独的消息,或者我应该尽可能地解析结果集并将消息分组(即字段名称和值匹配)到一个更大的更新语句中并将其作为消息传递
我最好直接通过Entity Framework更新还是使用存储过程?
答案 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对象:
您是否注意到,在本课程中我从未提及我使用数据库来获取或更新数据。这隐藏在委托函数中以获取和更新数据。这允许重用将数据存储在其他媒体中,如变量,流,文件等
作为一个例子:带学生的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();
},
}
虽然这是一个非常简洁且可重复使用的解决方案,但对于您想要显示的每个项目,它都需要做很多工作。如果要在工厂中自动执行此操作,则会遇到几个问题
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)
请注意,仅修改了已更改的属性,而名称不是。