В качестве примера у меня есть сущность Product, ProductViewModel и сущность Label. Два из моих свойств продукта соответствуют коду этикетки, а не фактическому значению. Так, например, «Название продукта» - это «code1234», что соответствует метке, в которой «code1234» является кодом, а «Milk» - значением. Метка не присоединяется как внешний ключ. Мы используем AutoMapper для проекций.
public class Product{
public int ProductId {get; set;}
public string Name {get; set;}
public string Description {get; set;}
}
public class ProductViewModel{
public int ProductId {get; set;}
public string Name {get; set;}
public string Description {get; set;}
}
public class Label{
public int LabelId {get; set;}
public string Code {get; set;}
public string Value {get; set;}
public int LanguageId {get; set}
}
Я ищу способ заменить
var currentLanguageId = 1; //As an example
IQueryable<Product> queryFromDb;
var Products = queryFromDb
.ProjectTo<ProductViewModel>().AsEnumerable();
foreach(var Product in Products) {
Product.Name = db.Labels.Where(x => x.Code == Product.Name && x.LanguageId == currentLanguageId).Single().Value;
Product.Description = db.Labels.Where(x => x.Code == Product.Description && x.LanguageId == currentLanguageId).Single().Value;
}
кодом, который будет держать запрос отложенным с момента фильтрации и сортировка выполняется по значению метки, соответствующему названию и описанию продукта, а не по самим наименованиям и описаниям, которые являются бессмысленными кодами.
Тогда мне также понадобится способ сделать все это обобщенным c, поскольку многие объекты в нашей базе данных имеют свойства, которые являются кодами меток.
То, что у меня есть до сих пор:
var result = scope.GetRepository<Product>().GetAll() //returns Queryable<Product>
.ProjectTo<ProductViewModel>(_mapper.ConfigurationProvider) //returns Queryable<ProductViewModel>
.WithLabels(_mapper, scope, x => x.Name, x => x.Description) //returns Queryable with columns replaced with a query
.ToDataResult(request); //sorts filters takes skips, etc.
public static IQueryable<T> WithLabels<T>(this IQueryable<T> instance,
IMapper mapper,
IBaseReadContextScope scope,
params Expression<Func<T, string>>[] expressions) where T : class
{
var currentLanguage = 1; //as an example
var labels = scope.GetRepository<Label>().GetAll(x => x.Language == currentLanguageId);
foreach (var expression in expressions)
{
var query = instance
.GroupJoin(
labels,
expression,
label => label.Code,
(x, y) => new { Obj = x, Label = y })
.SelectMany(
xy => xy.Label.DefaultIfEmpty(),
(x, y) => new { Obj = x.Obj, Label = y })
.Select(s => new ObjectWithLabel<T>()
{
Object = s.Obj,
Label = s.Label
});
instance = mapper.ProjectTo<T>(query, new { propertyName = ExpressionUtilities.PropertyName(expression) });
}
return instance;
}
CreateMap<ObjectWithLabel<ProductViewModel>, ProductViewModel>()
.ForMember(x => x.Name, m =>
{
m.Condition(x => propertyName == nameof(ProductViewModel.Name));
m.MapFrom(x => x.Label.Value);
})
.ForMember(x => x.Name, m =>
{
m.Condition(x => propertyName != nameof(ProductViewModel.Name));
m.MapFrom(x => x.Object.Name);
})
.ForMember(x => x.Description, m =>
{
m.Condition(x => propertyName == nameof(ProductViewModel.Description));
m.MapFrom(x => x.Label.Value);
})
.ForMember(x => x.Description, m =>
{
m.Condition(x => propertyName != nameof(ProductViewModel.Description));
m.MapFrom(x => x.Object.Description);
});
, которое фактически работает для одного свойства, но не для нескольких. Вдобавок ко всему, необходимость проецировать туда и обратно из ProductViewModel и ObjectWithLabel не велика. Причина, по которой я использую .Condition вместо простого троичного оператора, заключается в том, что ProjectTo не поддерживает выражения, и у меня возникают проблемы с работой UseAsDataSource () (что перевело бы это выражение для нас)
Идеи на данный момент :
Использовать перехватчики запросов. С помощью AutoMapper мы будем создавать строку, такую как INTERCEPTME_ColumnName_Code_Language, и заменять эту строку запросом (либо написанным в LINQ, либо в форме функции SQL). Кажется, что это будет работать, но есть и недостаток в том, что он не работает для модульных тестов с NO SQL (вероятно), и ему необходимо проверять каждый входящий запрос (если только нет способа пометить запрос как «перехват»).
Используйте метод, аналогичный моему текущему методу WithLabels, но создайте единственное объединение с несколькими столбцами внутри него, затем проецируйте только один раз из ObjectWithLabel s в ProductViewModel. (Понятия не имею, как обобщенное c объединение с неизвестным числом столбцов будет выглядеть так:
Найдите способ прямой замены столбца без использования промежуточного объекта / проекции путем создания запросов и отправка их в качестве параметра в AutoMapper, быстрая иллюстрация базовой идеи c:
Dictionary<string, IQueryable<string>> dictionary = null;
CreateMap<Product, ProductViewModel>()
.ForMember(x => x.Name, m => m.MapFrom(x =>
dictionary["Name"]))
.ForMember(x => x.Description, m => m.MapFrom(x =>
dictionary["Description"])));
Где будет построен словарь с запросами, которые возвращают значение Label.Value, соответствующее коду и желаемый язык.
Буду признателен за любой вклад в базовую проблему, которая заключается в замене столбца объекта в запросе, без выполнения этого запроса, а также с любым из потенциальных решений, которые я упомянул.
Спасибо