编辑:我最初的问题令人困惑和模棱两可,所以让我重新开始。
数据源是CSV文件的集合,因此没有实际的数据库。这是与日本数十年的旧系统的集成。
我有一个c#函数,需要使用2个DataTables
和2个列名作为参数。我的函数需要在这2个数据表上做一个INNER JOIN
的等效项,然后返回第一个表中的所有列,仅返回第二个表中的“联接列”。这些数据表的模式(读取:列)要到运行时才能知道,因此该函数不能有任何硬编码的列名。我的函数最后需要返回一个带有内部联接数据的新DataTable,以及一个基于刚刚指定的选择列表的DISTINCTed结果集。
这是我的[修改]尝试,似乎产生了有希望的结果集:
public static DataTable JoinDataTables2(DataTable dt1, DataTable dt2, string table1KeyField, string table2KeyField) {
DataTable result = ( from dataRows1 in dt1.AsEnumerable()
join dataRows2 in dt2.AsEnumerable()
on dataRows1.Field<string>(table1KeyField) equals dataRows2.Field<string>(table2KeyField)
select dataRows1).CopyToDataTable();
return result;
}
我这样称呼它:
Common.JoinDataTables2(dtCSV, _dtModelOptions, "CMODEL", "ModelID");
我的目标是像在物理数据库中一样执行内部联接,并根据上面指定的结果集使用不同的结果集。 您可能想知道为什么我不只是简单地进行联接在数据库中。这是因为没有数据库。数据来自由第三方系统生成的CSV文件。
所以我还有3个问题:
这是我系统中的一个具体示例,但同样,数据表和模式都将不同:
dtCSV 列:
_dtModelOptions 列:
我需要对功能进行哪些更改,以便:
foreach
遍历记录以实现联接,而这种方法非常慢。)感谢您的建议,我非常感谢大家的宝贵时间。
答案 0 :(得分:1)
早期状态...
public static DataTable JoinDataTables2(DataTable dt1, DataTable dt2, string table1KeyField, string table2KeyField) {
DataTable result = ( from dataRows1 in dt1.AsEnumerable()
join dataRows2 in dt2.AsEnumerable()
on dataRows1.Field<string>(table1KeyField) equals dataRows2.Field<string>(table2KeyField)
select new {Col1= datarows1Field<string>(table1FieldName), Col2= datarows2.Field<string>(table2FieldName)}).Distinct().CopyToDataTable();
return result;
}
您可以在select查询中列出table1中的所有列。以下查询具有按定义的DataTable,其中的数据表来自table1,而键列仅来自table2。可能会对您有帮助。
public static DataTable JoinDataTables2(DataTable dt1, DataTable dt2, string table1KeyField, string table2KeyField)
{
DataTable joinTable = new DataTable();
foreach (DataColumn dt1Column in dt1.Columns)
{
joinTable.Columns.Add(dt1Column.ColumnName, dt1Column.DataType);
}
var col2 = dt2.Columns[table2KeyField];
joinTable.Columns.Add(col2.ColumnName,typeof(string));
var result = (from dataRows1 in dt1.AsEnumerable()
join dataRows2 in dt2.AsEnumerable()
on dataRows1.Field<string>(table1KeyField) equals dataRows2.Field<string>(table2KeyField)
select new
{
Col1 = dataRows1,
Col2 = dataRows2.Field<string>(table2KeyField)
});
foreach (var row in result)
{
DataRow dr = joinTable.NewRow();
foreach (DataColumn dt1Column in dt1.Columns)
{
dr[dt1Column.ColumnName] = row.Col1[dt1Column.ColumnName];
}
dr[table2KeyField] = row.Col2;
joinTable.Rows.Add(dr);
}
joinTable.AcceptChanges();
return joinTable.AsEnumerable().Distinct().CopyToDataTable();
}
答案 1 :(得分:0)
[更新#3]
- 我不确定基于INNER JOIN行为返回的结果集是否正确。
linq查询返回的结果集完全代表您在查询中编写的内容。
- 选择列表不包含第二个数据表的“联接列”(在此特定示例中为“ ModelID”),并且我 需要它。
答案很简单:您的查询仅返回第一个数据表中的数据(顺便说一句:您已经在问题描述中提到了它)。
一旦这样做,我可以确认CMODEL值 匹配ModelID值,从而确认我具有有效的联接。 (这只是一种情况,会有所不同,因此没有列名可以 在功能中进行硬编码。)
您可以确定Linq2DataSet查询返回正确的ID。他们必须匹配才能加入他们。如果没有匹配项,则结果集将为空! 似乎,您必须提高对联接的了解。请阅读这篇出色的文章:Visual Representation of SQL Joins
相关文章的简短版本:
左加入
Set1 = [1, 2, 3, 5]
Set2 = [2, 4, 5]
Resultset = [1,2,5] //get [1] from left (set1), [2,5] are common items (set1 and set2)
内部加入
Set1 = [1, 2, 3, 5]
Set2 = [2, 4, 5]
Resultset = [2,5] //only common items (set1 and set2)
右加入
Set1 = [1, 2, 3, 5]
Set2 = [2, 4, 5]
Resultset = [2,4,5] // gets [2] from right (set2), [4,5] are common (set1 and set2)
交叉加入
cross join returns the cartesian product of the sets
- 如何区分结果集?
有一个Distinct method。
但是我不确定,您真的需要这个;(
一般说明:
有几种读取定界文件(* .csv)的方法:
1)使用“标准”读取文本文件方法,并将文本分成[for]循环
2),使用linq方法,即Select()
注意:大多数程序员都知道在处理大型数据集时linq方法比[for]循环要慢得多。
为了能够从联接表中投影字段,您必须使用:
select new {datarows1, datarows2}
如果您想使用Linq创建动态列,请参见:Query datatable with dynamic column names using LINQ
这是一个完整的代码,如何将两个数据表合并为一个 数据表:dotnetfiddle
3)使用OleDb:OleDbConnection,OleDbCommand
请参阅:
Using OleDb To Import Text Files tab CSV Custom
Read Text File Specific Columns
您的扩展方法可能类似于:
public static DataTable OleDbJoin(string csv1, string csv2, string key1, string key2)
{
DataTable dt = new DataTable();
string sConn = string.Format(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}\;Extended Properties='text;HDR=No;FMT=CSVDelimited()';", Path.GetDirectoryName(csv1));
string sSql = string.Format(@"SELECT T.*
FROM (
SELECT * FROM [{0}] AS t1
INNER JOIN (SELECT * FROM [{1}]) AS t2
ON t1.[{2}] = t2.[{3}]) AS T;",
Path.GetFileName(csv1), Path.GetFileName(csv2), key1, key2);
try
{
using (OleDbConnection oConn = new OleDbConnection(sConn))
{
using (OleDbCommand oComm = new OleDbCommand(sSql, oConn))
{
oConn.Open();
OleDbDataReader oRdr = oComm.ExecuteReader();
dt.Load(oRdr);
oComm.Dispose();
oRdr.Dispose();
oConn.Close();
oConn.Dispose();
}
}
}
catch(OleDbException ex)
{
Console.WriteLine(ex.Message);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
return dt;
}
致电:
DataTable resultDt = OleDbJoin("FullFileName1", "FullFileName2", "F1", "F2");
要求:
-两个csv文件必须位于同一目录中
-使用标准分隔符处理csv文件的csv文件,ee:Schema.ini file
-文件中没有标题(没有列名)
答案 2 :(得分:0)
有点歧义,但是据我了解,您需要Sub TestMe()
If Weekday(Now()) = vbMonday Then
SomeSelection
SomeSelection
SomeSelection
End If
End Sub
Sub SomeSelection()
'OP Code
End Sub
两个表并在对结果执行Sub TestMe()
Dim repeater As Long: repeater = 1
If Weekday(Now()) = vbMonday Then repeater = 3
Dim counter As Long
For counter = 1 To repeater
Range("B8").End(xlToRight).Offset(, 0).Select
'OP code...
ActiveSheet.Paste
Next
End Sub
之后从两个表(或更少)中获取一行。 Join
。考虑到这些列不是预定义的,所有这些。
这是我的解决方法:
添加一个Distinct()
类以包装Join
的结果
Result
添加一个Join
类以帮助您使用自己的逻辑来获取public class Result
{
public DataRow Table1Row { get; set; }
public DataRow Table2Row { get; set; }
public string DistictFieldValue { get; set; }
}
结果
ResultComparer
更新您的方法以使用上述类
Distinct()
答案 3 :(得分:0)
如果每个CSV文件都代表数据库的一个表,请考虑做类似于实体框架的事情。
让您的IQueryable<...>
实现DbSets
而不是IEnumerable<...>
如果只需要获取数据,这将非常容易。如果您还想更新,则需要实现(或重复使用)DbChangeTracker
public DbSet<T> : IEnumerable<T> where T: class
{
public FileInfo CsvFile {get; set;}
public IEnumerator<T> GetEnumerator()
{
return this.ReadCsvFile().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
protected IEnumerable<T> ReadCsvFile()
{
// open the CsvFile, read the lines and convert to objects of type T
// consider using Nuget package CsvHelper
...
foreach (var csvLine in csvLines)
{
T item = Create<T>(csvLine); // TODO: write how to convert a line into T
yield return T;
}
}
}
您还需要一个包含所有DbSet的DbContext:
class DbContext
{
public DbSet<School> Schools {get; } = new DbSet<School>{CsvFile = ...};
public DbSet<Teacher> Teachers {get; } = new DbSet<Teacher> {CsvFile = ...};
public DbSet<Student> Students {get; } = new DbSet<Student> {CsvFile = ...};
}
您可以通过记住已获取的项目来提高性能。将它们放在词典中,使用主键作为词典键。还要向DbSet中添加一个Find
函数:
class DbSet<T> : IEnumerable<T>
{
private readonly Dictionary<int, T> fetchedItems = new Dictionary<int, T>();
public T Find(int id)
{
if (!fetchedItems.TryGetValue(id, out T fetchedItem))
{
// fetch elements using ReadCsvFile and put them in the Dictionary
// until you found the item with the requested primary key
// or until the end of your sequence
}
return fetchedItem;
}
}
每个表项都具有相同类型的主键是最容易的:
interface IPrimaryKey
{
int Id {get;}
}
class DbSet<T> : IEnumerable<T> where T : IPrimaryKey {...}
否则,您需要告诉DbSet主键的类型:
class DbSet<T, TKey> : IEnumerable<T> where T : class
{
private readonly Dictinary<TKey, T> fetchedItems = ...
}
如果您决定将项目保留在Dictionary中,那么在从CSV文件中获取新行之前,让GetEnumerator首先返回已经获取的items。
为此,您需要能够从CsVFile添加/更新/删除项目。我认为已经有相应的功能。
要有效地执行更新,您将需要类似于DbContext.SaveChanges的内容。让每个DbSet都使用ChangeTracker记住要添加/删除/更新的项目:
class Entity<T> where T : IPrimaryKey
{
public T Value {get; set;}
public T OriginalValue {get; set;}
}
class ChangeTracker<T, TKey> where T: ICloneable
{
readonly Dictionary<int, Entity<T, TKey>> fetchedEntities = new Dictionary<int, Entity<T, TKey>>
readonly List<T> itemsToAdd = new List<T>();
public T Add(T item)
{
// TODO: check for not NULL, and Id == 0
this.ItemsToAdd.Add(itemToAdd);
return item;
}
public void Remove(T item)
{
// TODO: check not null, and primary key != 0
Entity<T> entityToRemove = Find(item.Id);
// TODO: decide what to do if there is no such item
entityToRemove.Value = null;
// null indicates it is about to be removed
}
您将需要一个可以记住原始值的查找:
public Entity<T> Find(TKey primaryKey)
{
// is it already in the Dictionary (found before)?
// if not: get it from the CsvDatabase and put it in the dictionary
if (!fetchedItems.TryGetValue(primaryKey, out Entity<T> fetchedEntity))
{
// not fetched yet, fetch if from your Csv File
T fetchedItem = ...
// what to do if does not exist?
// add to the dictionary:
fetchedEntities.Add(new Entity<T>
{
value = fetchedItem,
originalValue = (T)fetchedItem.Clone(),
// so if value changes, original does not change
});
}
return fetchedItem;
}
最后,您的SaveChanges()
void SaveChanges()
{
// your CsvFile database has functions to add / update / remove items
foreach (var itemToAdd in itemsToAdd)
{
csvDatabase.Add(itemToAdd);
}
// update or remove fetched items with OriginalValue unequal to Value
var itemsToUpdate = this.fetchedItems
.Where(fetchedItem => !ValueComparer.Equals(fetchedItem.OriginalValue, fetchedItem.Value)
.ToList();
foreach (Entity<T> itemToUpdate in itemsToUpdate)
{
if (itemToUpdate.Value == null)
{ // remove
csvFile.Remove(itemToUpdate.OriginalValue);
}
else
{ // update
csvFile.Update(...);
}
}
}
显然,如果您希望能够更新数据库中的项目,则需要能够检查项目是否已更改。您需要一个IEqualityComparer<T>
来按值检查
class DbChangeTracker<T, TKey> : IEnumerable<T> where T : class
{
public IEqualityComparer<T> ValueComparer {get; set;}
...
}
DbSet保存更改:
void SaveChanges()
{
this.ChangeTracker.SaveChanges();
}
DbContext保存更改:
Students.SaveChanges()
Teachers.SaveChanges();
Schools.SaveChanges();