#c# #lambda
#c# #лямбда
Вопрос:
У меня есть List<imports>
который создается путем чтения CSV-файла. У меня есть List<table>
путем чтения из таблицы базы данных. Каков был бы правильный способ настройки лямбда-выражений на:
- Найдите пересечение (Записи для ОБНОВЛЕНИЯ или записи без ДЕЙСТВИЙ)
- Найдите новые элементы в списке (записи для ВСТАВКИ)
- Найдите элементы в списке, которых нет в списке (записи для УДАЛЕНИЯ)
Прямо сейчас я пробиваюсь через это следующим образом:
foreach (DTO.ImportData row in Helper.ImportTracker.ImportsValid)
{
bool isInsert = false;
bool isUpdate = false;
Model.Auto auto = null;
// Get auto(s) for this SKU VIN ClientID...
var autos = _dbFeed.Autoes.Where(a => a.StockNumber == row.Stock amp;amp; a.VIN == row.VIN amp;amp; a.ClientID == _targetClientID amp;amp; a.SourceClientID == _sourceClientID).ToList();
if (autos.Count > 1) // ERROR...
{
Helper.ImportTracker.ImportsInvalid.Add(row);
continue;
}
else if (autos.Count == 1) // UPDATE...
{
auto = autos[0];
if (auto.GuaranteedSalePrice != row.GuaranteedSalePrice ||
auto.ListPrice != row.ListPrice ||
auto.Miles != row.Miles ||
auto.Active != row.Active ||
auto.MSRP != row.MSRP ||
auto.InternetPrice != row.Internet_Price ||
auto.InvoiceCost != row.Invoice ||
auto.Make != row.Make ||
auto.Model != row.Model ||
auto.Year != row.Year
)
{
Helper.ImportTracker.Updates.Add(row);
isUpdate = true;
}
else
{
isUpdate = false;
auto = null;
}
}
else // INSERT...
{
isInsert = true;
auto = new Model.Auto();
_dbFeed.Autoes.AddObject(auto);
Helper.ImportTracker.Inserts.Add(row);
}
// Fill in the data...
if (auto != null)
{
...
}
// left out for readability - this section just maps the import
// data to the table row and saves to the DB...
}
В приведенном выше разделе рассматриваются первые 2 случая, которые я перечислил в начале.
У меня чертовски много времени, чтобы обдумать правильный способ объединения лямбда-выражений для этого.
Я понимаю, что, возможно, мне придется преобразовать все мои List<import>
в List<table>
, чтобы я мог сравнивать яблоки с яблоками, и это не проблема. Я также думаю, что мне нужно написать пользовательский компаратор в соответствии с:
class TableComparer : IEqualityComparer<table>
{
public bool Equals(table x, table y)
{
if (Object.ReferenceEquals(x, y)) return true;
if (Object.ReferenceEquals(x, null) ||
Object.ReferenceEquals(y, null))
return false;
return x.SKU == y.SKU amp;amp; x.VIN == y.VIN amp;amp; x.ClientID == y.ClientID;
}
public int GetHashCode(table table)
{
if (Object.ReferenceEquals(table, null)) return 0;
int hashSKU = SKU == null ? 0 : SKU.GetHashCode();
int hashVIN = VIN == null ? 0 : VIN.GetHashCode();
int hashClientID = ClientID.GetHashCode();
return hashClientID ^ hashSKU ^ hashVIN;
}
}
Тогда я могу сделать:
var UpdateAutos = autos.Intersect(new TableComparer(imports));
var InsertAutos = imports.Except(new TableComparer(autos));
var DeleteAutos = autos.Except(new TableComparer(imports));
И теперь у меня голова идет кругом! 😉
Я на правильном пути?
ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ: Пока что я продвинулся так далеко со своим новым кодом:
private void HandleAutos()
{
// convert to List<auto>...
List<Model.Auto> imports = AutoConvert.Convert(Helper.ImportTracker.ImportsValid, _targetClientID, _sourceClientID, DateTime.UtcNow, _dbFeed);
// get all DB records in List<auto>...
List<Model.Auto> current = _dbFeed.Autoes.Where(a => a.ClientID == _targetClientID amp;amp; a.Active == true).ToList();
// isolate all Inserts, Updates and Deletes...
var intersect = imports.Intersect(current, new AutoIsIn()); // should be all autos with matching VIN amp; SKU //
var updates = intersect.Intersect(current, new AutoHasChanged()); // should be a subset of changed resords //
var inserts = imports.Except(current, new AutoIsIn()); // should be all the imports not in the DB //
var deletes = current.Except(imports, new AutoIsIn()); // should be all the DB records not in imports //
}
И мой класс Comparer выглядит следующим образом:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace RivWorks.FeedHandler.Library
{
class AutoIsIn : IEqualityComparer<Model.Auto>
{
public bool Equals(Model.Auto x, Model.Auto y)
{
if (Object.ReferenceEquals(x, y)) return true;
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) return false;
return x.StockNumber == y.StockNumber amp;amp; x.VIN == y.VIN;
}
public int GetHashCode(Model.Auto auto)
{
if (Object.ReferenceEquals(auto, null)) return 0;
int hashSKU = auto.StockNumber == null ? 0 : auto.StockNumber.GetHashCode();
int hashVIN = auto.VIN == null ? 0 : auto.VIN.GetHashCode();
return hashSKU ^ hashVIN;
}
}
class AutoHasChanged : IEqualityComparer<Model.Auto>
{
public bool Equals(Model.Auto x, Model.Auto y)
{
if (Object.ReferenceEquals(x, y)) return true;
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) return false;
return (x.GuaranteedSalePrice != y.GuaranteedSalePrice
|| x.ListPrice != y.ListPrice
|| x.Miles != y.Miles
|| x.MSRP != y.MSRP
|| x.InternetPrice != y.InternetPrice
|| x.InvoiceCost != y.InvoiceCost
|| x.Make != y.Make
|| x.Model != y.Model
|| x.Year != y.Year
);
}
public int GetHashCode(Model.Auto auto)
{
if (Object.ReferenceEquals(auto, null)) return 0;
int hashMake = auto.Make == null ? 0 : auto.Make.GetHashCode();
int hashModel = auto.Model == null ? 0 : auto.Model.GetHashCode();
int hashYear = auto.Year.GetHashCode();
int hashGSP = auto.GuaranteedSalePrice.GetHashCode();
int hashLP = !auto.ListPrice.HasValue ? 0 : auto.ListPrice.GetHashCode();
int hashMiles = !auto.Miles.HasValue ? 0 : auto.Miles.GetHashCode();
int hashMSRP = !auto.MSRP.HasValue ? 0 : auto.MSRP.GetHashCode();
int hashIP = !auto.InternetPrice.HasValue ? 0 : auto.InternetPrice.GetHashCode();
int hashIC = !auto.InvoiceCost.HasValue ? 0 : auto.InvoiceCost.GetHashCode();
return hashMake ^ hashModel ^ hashYear ^ hashGSP ^ hashLP ^ hashMiles ^ hashMSRP ^ hashIP ^ hashIC;
}
}
}
Пока что-нибудь не так?
-кб
Комментарии:
1. Кажется, вы на правильном пути. Было бы предпочтительнее загружать ваши записи в один и тот же объект / DTO, чтобы обеспечить простоту манипуляций. У меня нет под рукой примера кода, поэтому я не буду публиковать его в качестве ответа, но вы можете расширить лямбда-выражения, чтобы объединить несколько лямбда-выражений в синтаксисе AND / OR. Это может быть очень удобно. Если вы этого хотите, я попытаюсь это выяснить.
2. Это было бы потрясающе. Я понимаю, что использую одни и те же объекты / DTO, поэтому я рассматриваю возможность создания IEqualityComparer для этого (поскольку я использую EF2 или EF3, а не EF4)…
3. Кроме того, я создал простое консольное приложение для игры с Intersect, Except и т.д. И не совсем получаю результаты, которые ожидал. Достаточно сказать, что я все еще нахожусь на крутом этапе освоения лямбда-выражений за пределами простого LINQ…
Ответ №1:
Не решение проблемы OPs, а в ответ на комментарии OPs…
Public Module ExpressionExtensions
<System.Runtime.CompilerServices.Extension()> _
Public Function Compose(Of T)(ByVal first As Expressions.Expression(Of T), ByVal second As Expressions.Expression(Of T), ByVal merge As Func(Of Expressions.Expression, Expressions.Expression, Expressions.Expression)) As Expressions.Expression(Of T)
' build parameter map (from parameters of second to parameters of first)
Dim map = first.Parameters.[Select](Function(f, i) New With {f, .s = second.Parameters(i)}).ToDictionary(Function(p) p.s, Function(p) p.f)
' replace parameters in the second lambda expression with parameters from the first
Dim secondBody = ParameterRebinder.ReplaceParameters(map, second.Body)
' apply composition of lambda expression bodies to parameters from the first expression
Return Expressions.Expression.Lambda(Of T)(merge(first.Body, secondBody), first.Parameters)
End Function
<System.Runtime.CompilerServices.Extension()> _
Public Function [And](Of T)(ByVal first As Expressions.Expression(Of Func(Of T, Boolean)), ByVal second As Expressions.Expression(Of Func(Of T, Boolean))) As Expressions.Expression(Of Func(Of T, Boolean))
Return first.Compose(second, AddressOf Expressions.Expression.And)
End Function
<System.Runtime.CompilerServices.Extension()> _
Public Function [Or](Of T)(ByVal first As Expressions.Expression(Of Func(Of T, Boolean)), ByVal second As Expressions.Expression(Of Func(Of T, Boolean))) As Expressions.Expression(Of Func(Of T, Boolean))
Return first.Compose(second, AddressOf Expressions.Expression.[Or])
End Function
End Module
Редактировать: добавлен отсутствующий ParameterRebinder
Public Class ParameterRebinder
Inherits Expressions.ExpressionVisitor
Private ReadOnly map As Dictionary(Of Expressions.ParameterExpression, Expressions.ParameterExpression)
Public Sub New(ByVal map As Dictionary(Of Expressions.ParameterExpression, Expressions.ParameterExpression))
Me.map = If(map, New Dictionary(Of Expressions.ParameterExpression, Expressions.ParameterExpression)())
End Sub
Public Shared Function ReplaceParameters(ByVal map As Dictionary(Of Expressions.ParameterExpression, Expressions.ParameterExpression), ByVal exp As Expressions.Expression) As Expressions.Expression
Return New ParameterRebinder(map).Visit(exp)
End Function
Protected Overloads Overrides Function VisitParameter(ByVal p As Expressions.ParameterExpression) As Expressions.Expression
Dim replacement As Expressions.ParameterExpression = Nothing
If map.TryGetValue(p, replacement) Then
p = replacement
End If
Return MyBase.VisitParameter(p)
End Function
End Class
Вышесказанное позволяет вам иметь…
Dim A as System.Func(Of MyType, Boolean) = Function(x) x.SomeField = SomeValue
Dim B as System.Func(Of MyType, Boolean) = A.Or(Function(x) x.SomeOtherField = SomeOtherValue)
Dim C as System.Func(Of MyType, Boolean) = A.And(Function(x) x.SomeOtherField = SomeOtherValue)
Я явно ввел приведенное выше для наглядности. Это не обязательно.
Приношу извинения за то, что нахожусь в VB — у меня есть код на руках, и сейчас у меня нет времени переводить
Комментарии:
1. Спасибо. Просматриваю это в течение следующих нескольких дней. И не беспокойтесь о VB, я говорю на двух языках. 😉
2. @Keith Не беспокойтесь, надеюсь, это поможет. Я только что понял, что не включил
ParameterRebinder
класс — я отредактировал свой ответ, чтобы включить его. Дайте мне знать, если у вас возникнут какие-либо проблемы3. хорошо — я думал, что знаю достаточно. Вам было бы сложно перевести его на C #?
4. И это позволило бы мне передать мои 2 объекта (один пустой) и скопировать все из полного в пустой?
5. Да, я часто использую это, но оно имеет тенденцию перегружать код лямбдами — я также использую это выражение, которое поддерживает .Net 4 и лямбды, но немного строже