Как получить список из базы данных на основе другого списка другого типа объекта? - PullRequest
0 голосов
/ 04 июля 2018

У меня есть две модели:

public class Product
{
    public int Id { get; set; }
    public int ProductGroupId { get; set; }
    public int ProductGroupSortOrder { get; set; }
    // ... some more properties
    public ICollection<ProductInCategory> InCategories { get; set; }
}

public class ProductInCategory
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public int ProductCategoryId { get; set; }
    public int SortOrder { get; set; }

    // Nav.props.:
    public Product Product { get; set; }
    public ProductCategory ProductCategory { get; set; }
}

Некоторые из Product сгруппированы через свойство ProductGroupId, и я хочу иметь возможность удалять целые группы Product с из ProductInCategory в одном запросе Db.

Метод контроллера получает product_id и category_id, а не ProductGroupId.

Для одного Product Я использовал этот запрос, чтобы удалить его из категории:

ProductInCategory unCategorize = await _context.ProductsInCategories
    .Where(pic => pic.ProductId == product_id && pic.ProductCategoryId == category_id)
    .FirstOrDefaultAsync();

и затем:

_context.Remove(unCategorize);
await _context.SaveChangesAsync();

Теперь, если у меня есть List<Product>, который я хочу удалить из ProductsInCategories, как будет выглядеть запрос?

Я пробовал это, но он не работает на .Any() -бит:

Product product = await _context.Products
    .Where(p => p.Id == product_id)
    .FirstOrDefaultAsync();

List<Product> products = await _context.Products
    .Where(g => g.ProductGroupId == product.ProductGroupId)
    .ToListAsync();

List<ProductInCategory> unCategorize = await _context.ProductsInCategories
    .Where(pic => pic.ProductId == products.Any(p => p.Id)
        && pic.ProductCategoryId == category_id)
    .ToListAsync();

Ответы [ 4 ]

0 голосов
/ 04 июля 2018

Метод контроллера получает product_id и category_id, а не ProductGroupId

Первый вопрос: почему метод получает product_id, тогда как ему нужно что-то делать с ProductGroupId.

Это пахнет плохим дизайном, но в любом случае, давайте сначала переведем product_id в желаемый ProductGroupId (это будет стоить нам дополнительного запроса в БД):

int? productGroupId = await _context.Products
    .Where(p => p.Id == product_id)
    .Select(p => (int?)p.ProductGroupId)
    .FirstOrDefaultAsync();

if (productGroupId == null)
{
    // Handle non existing product_id 
}

Остальное - просто доступ к свойству навигации в запросе LINQ to Entities, которое EF Core преобразует в соответствующее объединение внутри сгенерированного SQL-запроса. Никакой промежуточный список Product не требуется.

List<ProductInCategory> unCategorize = await _context.ProductsInCategories
    .Where(pic => pic.Product.ProductGroupId == productGroupId)
    .ToListAsync();
0 голосов
/ 04 июля 2018

Попробуйте изменить на следующее:

List<ProductInCategory> unCategorize = await _context.ProductsInCategories
    .Where(pic => products.Select(p => p.Id).Contains(pic.ProductId)
        && pic.ProductCategoryId == secondary_id)
    .ToListAsync();
0 голосов
/ 04 июля 2018

Я мог бы предложить исправление кода для того, что вы хотите, но есть лучшее решение: n не загружать данные, чтобы начать с .

В вашем примере кода вы загружаете данные из базы данных, а затем говорите EF удалить загруженные элементы. Это не эффективно. Не должно быть никаких причин для загрузки данных, вы должны иметь возможность просто выполнить запрос без необходимости загружать данные.

Насколько мне известно, Entity Framework не способен к "условному удалению" (из-за отсутствия лучшего имени), например:

DELETE FROM People WHERE Name = 'Bob' 

Если вы хотите удалить элементы на основе определенного значения столбца (кроме идентификатора объекта), вы не можете полагаться на Entity Framework, если не хотите загружать данные (что снижает производительность).

Здесь есть два лучших варианта:

1. Выполните SQL-запрос самостоятельно

context.Database.ExecuteSqlCommand(
        "DELETE FROM Products WHERE ProductGroupId = " + product.ProductGroupId 
      ); 

Так я всегда делал.

Sidenote: Я ожидаю комментариев о внедрении SQL. Для ясности: здесь нет опасности внедрения SQL-кода, поскольку product.ProductGroupId не является строкой, а ее значение контролируется разработчиком, а не конечным пользователем.
Тем не менее, я согласен, что использование параметров SQL является хорошей практикой. Но в этом ответе я хотел предоставить простой пример, демонстрирующий, как выполнить строку, содержащую SQL.

2. Найдите библиотеку, которая позволяет удалять без загрузки.

Я только споткнулся об этом, когда гуглял только сейчас. Расширения Entity Framework * Кажется, что в 1036 * реализована функция условного удаления:

context.Customers.Where(x => x.ID == userId).DeleteFromQuery();

В вашем случае это будет:

_context.Products.Where(g => g.ProductGroupId == product.ProductGroupId).DeleteFromQuery();

Sidenote
Я всегда использовал Code First, а EF всегда генерировал каскадные удаления для меня автоматически. Поэтому, когда вы удаляете родителя, его потомки также удаляются. Я не уверен, что ваша база данных каскадно удаляет, но я предполагаю поведение EF по умолчанию (согласно моему опыту).

0 голосов
/ 04 июля 2018

Возможно, значения products равны нулю в предыдущем запросе LINQ. добавьте еще одно условие для подтверждения этого.

.Where(pic => products && pic.ProductId == products.Any(p => p.Id)
    && pic.ProductCategoryId == secondary_id)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...