Динамические фильтры в entity framework не отфильтровывают сущности при их чтении сразу после обновления.

Я использую динамические фильтры в Entity Framework. Это метод расширения DBModelBuilder.Filter в EntityFramework.DynamicFilters. Когда я изменяю сущность, которая должна быть отфильтрована, в следующем запросе с использованием того же контекста БД она все еще видна при чтении через коллекции навигации.

Это нормальное поведение? Есть ли способ исправить это, не меняя способ обновления и чтения данных в приложении?

Ниже приведен пример консольного приложения, показывающего мою проблему.

Он отлично работает, когда я использую разные экземпляры ProductDbContext для косвенного чтения продуктов через категории или когда я читаю категории непосредственно из того же контекста БД.

Сущности и контекст БД:

public enum Status
{
    New = 0,
    Used = 1
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Status Status { get; set; }

    public int CategoryId { get; set; }
    public virtual Category Category { get; set; }

    public override string ToString()
    {
        return $"Id = {Id}, Name = {Name}, Status = {Status.ToString()}";
    }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Product> Products { get; set; }
}

public class ProductDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<Category> Categories { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Filter("OnlyNewProducts", (Product p) => p.Status == Status.New);
    }
}

Категории:

-------------------
| Id | Name       |
-------------------
| 3  | Category 1 |
-------------------

Продукты:

----------------------------------------
| Id | Name      | Status | CategoryId |
----------------------------------------
| 3  | Product 1 | 0      | 3          |
----------------------------------------

Класс, показывающий мою проблему:

class Program
{
    static ProductDbContext ctx = new ProductDbContext();

    static void Main(string[] args)
    {
        Read();
        Modify();
        Read();
    }

    private static void Modify()
    {
        var product = ctx.Products.FirstOrDefault(p => p.Name == "Product 1");
        product.Status = Status.Used;
        ctx.Entry(product).State = EntityState.Modified;
        ctx.SaveChanges();
    }

    private static void Read()
    {
        var newProducts = ctx.Categories.FirstOrDefault(c => c.Name == "Category 1").Products.ToList();

        foreach (var newProduct in newProducts)
        {
            Console.WriteLine(newProduct.ToString());
        }
    }
}

Результат:

Id = 3, Name = Product 1, Status = New 
Id = 3, Name = Product 1, Status = Used

Обновленный объект со статусом "используется" не должен отображаться.

Я нашел обходной путь, позвонив в ((IObjectContextAdapter)ctx).ObjectContext.Refresh(RefreshMo‌​de.StoreWins, product) после сохранения изменений, но это не то, что я ищу.

alcohol is evil 11.04.2018 15:48
0
1
1 197
2

Ответы 2

Поначалу этого делать необязательно:

ctx.Entry(product).State = EntityState.Modified;

В этом случае EF автоматически помечает объект как измененный (исходящий из контекста, поэтому это прокси-объект).

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

static void Main(string[] args)
{
    Read();
    Modify();
    ctx = new Product2DbContext();
    Read();
}

Я знаю, это некрасиво. Вы можете добавить проблему здесь: https://github.com/zzzprojects/EntityFramework.DynamicFilters/issues

Другое редактирование: После исследования динамический фильтр использует перехватчик для непосредственной перезаписи SQL-запроса. Когда вы загружаете свой продукт в категорию в первый раз, перехватчик выполняет свою работу, потому что он отправляет команду SQL. Во второй раз, когда вы пытаетесь получить доступ к продуктам, категория уже известна в вашем контексте и продукты тоже. Таким образом, EF не нужно выполнять запрос (поведение LazyLoading). Так что перехватчик не называется. Я не знаю, можете ли вы принудительно обновить данные, я пытаюсь безуспешно.

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

alcohol is evil 11.04.2018 15:01

На мой взгляд, это не ошибка. Это не цель ORM. Вы можете создать паттерн, который содержит все ваши данные или только изменен. Когда вы запрашиваете эти данные, вы можете проверить свой SQL-сервер и свои измененные данные и объединить все, чтобы извлечь то, что вы хотите. Время жизни контекста должно быть сокращено по возможности.

Cedric 11.04.2018 15:09

@alcoholisevil Как вы думаете, почему это ошибка EF, а не ошибка / ограничение используемого вами пакета Третья сторона? EF не поддерживает фильтры, поэтому я не понимаю вашей точки зрения.

Ivan Stoev 11.04.2018 15:13

@IvanStoev, ты прав. Это не ошибка EF, но я все же думаю, что это ошибка (или ограничение, как вы предложили). Если я хочу видеть обновленные объекты, я могу легко отключить фильтры. Я не могу представить, как снова фильтровать все прочитанные данные, когда у меня определены глобальные фильтры. В моем примере приложения это было бы просто, но не в веб-приложении с репозиториями и службами, использующими один и тот же экземпляр DbContext для всего HTTP-запроса.

alcohol is evil 11.04.2018 15:20

Неужели измененные данные нельзя сохранить? Если нет, почему бы напрямую не сохранить ваш контекст? Я понимаю, что вы имеете в виду, у меня с этим такая же проблема. Я постараюсь исправить это, добавив слой между моей третьей стороной и моим EF для инкапсуляции измененных данных.

Cedric 11.04.2018 15:24

Я не уверен, что понимаю, что вы имеете в виду. Я сохраняю изменения DbContext сразу после изменения объекта и перед его повторным чтением.

alcohol is evil 11.04.2018 15:27

Ой ! Мне очень жаль: $ Я пропустил ваш SaveChanges (). Я понимаю вашу просьбу. Я исправлю свой пост как можно скорее.

Cedric 11.04.2018 15:35

@alcoholisevil Все, что я говорил, это проблема / ошибка / ограничение библиотеки EntityFramework.DynamicFilters. Риски программного обеспечения с открытым исходным кодом.

Ivan Stoev 11.04.2018 16:11

@ Седрик, спасибо за ответ. Помогает создание нового экземпляра DbContext. Я упомянул об этом в своем вопросе и спросил, как решить проблему, не делая этого, поэтому, к сожалению, я не буду использовать ваше решение, но я уверен, что оно поможет другим людям, имеющим такую ​​же проблему.

alcohol is evil 12.04.2018 11:45

Да, я знаю, что это ужасное решение. Но это работа. Надеюсь, вы сможете найти более полезное решение. Пожалуйста, опубликуйте свое решение, которое вы выберете, когда закончите. Будет интересно посмотреть, как вы исправите свою проблему.

Cedric 12.04.2018 14:25

ПРИМЕЧАНИЕ. Так работает Linq. У меня нет опыта работы с Entity.

Вроде того, что написал Седрик ранее, и комментарий [алкоголь - это зло]:

private static void Modify()
{
    var product = ctx.Products.FirstOrDefault(p => p.Name == "Product 1");
    product.Status = Status.Used;
    ctx.Entry(product).State = EntityState.Modified;
    ctx.SaveChanges();
    ctx.Refresh(RefreshMode.OverwriteCurrentValues, product);
    product = ctx.Products.FirstOrDefault(p => p.Name == "Product 1");
}

Я не использовал Entity, но это то, что работает в Linq.

Refresh (опять же, в Linq) перегружен, чтобы принять product или пропустить его. Я обнаружил, что если я пропущу его, мне придется перезагрузить product, как показано в следующей строке.

Надеюсь, это поможет.

jp2code, является ли метод Refresh методом расширения для класса DbContext? Нужна ли ссылка на другую библиотеку? В любом случае, в вашем примере обновление не требуется, потому что нет проблем с чтением измененных продуктов непосредственно из DbContext. Проблема заключается в чтении измененных продуктов через свойство навигации другого класса.

alcohol is evil 12.04.2018 11:52
Обновить является членом класса DataContext. Он перегружен опциями. Я не был уверен, что это сработает, но это может дать вам представление о том, как обновить элементы управления.
jp2code 12.04.2018 15:44

Другие вопросы по теме