Почему на мою сущность ссылаются несколько экземпляров IEntityChangeTracker? - PullRequest
0 голосов
/ 12 марта 2019

Я пытаюсь внедрить современные практики (с использованием сервисов, IoC и т. Д.) В моем проекте ASP.NET Web Forms, внедряя мой DbContext в класс сервиса, но продолжаю получать сообщение об ошибке An entity object cannot be referenced by multiple instances of IEntityChangeTracker.

Это меня смущает, потому что я относительно уверен, что у меня есть только одно отслеживание изменений DbContext.

В чем проблема? Я использовал статические классы, которым я отправил DbContext в качестве параметров функции, но кроме этого, я ничего не изменил на стороне страницы ...

public partial class Create : System.Web.UI.Page
{
    private LicenseService licenseService;

    private ApplicationDbContext context = new ApplicationDbContext();
    protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            licenseService = new LicenseService(context);
        }
        catch (Exception ex)
        {
            // TODO Add error handling/logging.
            ErrorMessage.Text = ex.Message;
        }
    }
    protected void Page_LoadComplete(object sender, EventArgs e)
    {
        this.context.Dispose();
    }

    protected async void Save_Click(object sender, EventArgs e)
    {
        try
        {
            var userManager = Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
            var user = await userManager.FindByIdAsync(User.Identity.GetUserId());

            await SaveLicenseAsync(user);

            // Show a message that the work was done.
            ShowSnackbar("License was successfully added; redirecting...", "Default");
        }
        catch (Exception ex)
        {
            // TODO Add error handling/logging.
            ErrorMessage.Text = ex.Message;
        }
    }

    private async Task SaveLicenseAsync(ApplicationUser user)
    {
        // Get the specified expiry date.
        DateTime.TryParse(ExpiryDatePicker.SelectedDate.ToString(), out DateTime expiryDate);

        // Create the viewmodel that will be passed to the service.
        var model = new LicenseViewModel
        {
            ExpiryDate = expiryDate,
            Name = NameTextBox.Text
        };
        await licenseService.AddAsync(model, user);
    }
}

Мой сервис обычно взаимодействует с EF. Мне это нравится, потому что он отделяет страницу от логики DbContext. Я не люблю смешивать это. Вот как выглядит сервис:

публичный класс LicenseService { закрытый контекст ApplicationDbContext; public LicenseService (ApplicationDbContext db) { context = db; }

    public List<LicenseViewModel> Get()
    {
        var factory = new LicenseViewModelFactory();

        var licenses = context.Licenses.ToList();
        return factory.GetViewModelList(licenses);
    }
    public List<LicenseViewModel> Get(string userId)
    {
        var factory = new LicenseViewModelFactory();

        var licenses = context.Licenses.Where(x => x.User.Id == userId).ToList();
        return factory.GetViewModelList(licenses);
    }
    public LicenseViewModel Get(int licenseId)
    {
        var factory = new LicenseViewModelFactory();
        var license = context.Licenses.SingleOrDefault(x => x.Id == licenseId);
        return factory.GetViewModel(license);
    }

    public async Task AddAsync(LicenseViewModel model, ApplicationUser owner)
    {
        var license = new License
        {
            ExpiryDate = model.ExpiryDate,
            Name = model.Name,
            User = owner
        };
        context.Licenses.Add(license);
        await context.SaveChangesAsync();
    }
    public async Task AddRangeAsync(IEnumerable<LicenseViewModel> models, ApplicationUser owner)
    {
        var list = new List<License>();
        foreach (var model in models)
        {
            list.Add(new License
            {
                ExpiryDate = model.ExpiryDate,
                Name = model.Name,
                User = owner
            });
        }
        context.Licenses.AddRange(list);
        await context.SaveChangesAsync();
    }
    public async Task UpdateAsync(LicenseViewModel model)
    {
        var license = context.Licenses.Single(x => x.Id == model.Id);
        license.ExpiryDate = model.ExpiryDate;
        license.Name = model.Name;

        await context.SaveChangesAsync();
    }
    public async Task DeleteAsync(LicenseViewModel model)
    {
        var license = context.Licenses.Single(x => x.Id == model.Id);
        context.Licenses.Remove(license);
        await context.SaveChangesAsync();
    }
    public async Task DeleteAsync(int licenseId)
    {
        await DeleteAsync(Get(licenseId));
    }
}

1 Ответ

0 голосов
/ 23 марта 2019

Проблема будет с вашей ссылкой пользователя в лицензии. Это извлекается из контекста Овина, но вы пытаетесь сохранить / связать его с контекстом вашей лицензии.

Я бы обновил метод, чтобы просто принять идентификатор пользователя, а затем загрузить и связать пользователя из контекста вашей лицензии.

public async Task AddRangeAsync(IEnumerable<LicenseViewModel> models, int ownerId)
{
    var list = new List<License>();
    var owner = context.Users.Single(x => x.UserId == ownerId);
    foreach (var model in models)
    {
        list.Add(new License
        {
            ExpiryDate = model.ExpiryDate,
            Name = model.Name,
            User = owner
        });
    }
    context.Licenses.AddRange(list);
    await context.SaveChangesAsync();
}

Это должно решить вашу проблему. Подлая маленькая деталь. :)

Чтобы избежать нагрузки, вы можете использовать:

var owner = context.Owners.Local.SingleOrDefault(x => x.UserId == ownerId);
if (owner == null)
{
    owner = new User{UserId = ownerId};
    context.Users.Attach(owner);
}
// ...

Я, вероятно, рассмотрю возможность размещения этого в методе базового класса, чтобы получить ссылку на владельца из контекста. Предостережение этого подхода заключается в том, что пользователь из этого контекста не будет содержать подробных сведений о пользователе, если вы явно не загрузите его из БД. (Т. Е. Имя и т. Д.) Так что все в порядке с перформанса П.о.В. до тех пор, пока команда знает, что нельзя доверять тому, что у контекста будет полный пользователь, если он явно не перезагружен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...