为什么我的实体被IEntityChangeTracker的多个实例引用? [英] Why is my entity being referenced by multiple instances of IEntityChangeTracker?

查看:60
本文介绍了为什么我的实体被IEntityChangeTracker的多个实例引用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图通过将DbContext注入服务类中来在我的ASP.NET Web窗体项目中实现现代实践(使用服务,IoC等),但是我一直收到错误消息,说实体对象不能由IEntityChangeTracker的多个实例引用.

I'm trying to implement modern practices (using services, IoC, etc.) in my ASP.NET Web Forms project by injecting my DbContext into a service class but I keep getting an error saying An entity object cannot be referenced by multiple instances of IEntityChangeTracker.

这让我感到困惑,因为我相对确定我只有一个DbContext跟踪更改.

This is confusing me because I'm relatively sure I only have a single DbContext tracking changes.

出什么问题了?我使用的静态类将DbContext发送为函数参数,但除此之外,我没有在页面侧进行任何更改...

What's the problem? I was using static classes that I sent the DbContext to as function parameters but other than that, I haven't changed anything on the page side...

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逻辑中分离出来.我不喜欢混在一起.服务如下所示:

My service generally has all the interaction with EF in it. This I like because it separates the page out of the DbContext logic. I don't like to mix it up. Here's what the service looks like:

公共类LicenseService{私有ApplicationDbContext上下文;公共LicenseService(ApplicationDbContext db){上下文= db;}

public class LicenseService { private ApplicationDbContext context; 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));
    }
}

推荐答案

问题将与您在Licence中的用户参考有关.这是从Owin上下文中检索到的,但是您试图将其与您的License上下文保存/关联.

The issue will be with your User reference in Licence. That is retrieved from the Owin context, but you are trying to save/associate it with your Licence context.

我将更新该方法以仅接受用户ID,然后从您的许可上下文中加载并关联该用户.

I'd update the method to simply accept the user ID then load and associate the user from your Licence context.

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();
}

那应该可以解决您的问题.鬼little的小细节.:)

That should resolve your issue. Sneaky little detail. :)

为避免负载,您可以使用:

To avoid the load you could use:

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

我可能会考虑将其放在基类方法中,以便从上下文中检索所有者引用.该方法的警告是,除非您从数据库中明确加载了该上下文,否则该上下文中的用户将不包含有关该用户的详细信息.(即名称等),所以从表演P.o.V.开始就可以了只要团队知道不信任该上下文将具有完整的User,除非明确重新加载.

I'd probably consider placing that in a base class method to retrieve an owner reference from the context. The caveat of that approach is that the user from this context will not contain details about the user unless you explicitly load it from the DB. (I.e. Name, etc.) So it's OK from a performance P.o.V. so long as the team knows not to trust that the context will have a complete User unless explicitly reloaded..

这篇关于为什么我的实体被IEntityChangeTracker的多个实例引用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆