【问题标题】:Creating an EF Core entity with an existing foreign property with MVVM使用 MVVM 创建具有现有外部属性的 EF Core 实体
【发布时间】:2026-01-03 16:40:01
【问题描述】:

我将 MVVM 与 EF Core 一起使用。我有一个实体,它具有预先播种到数据库的外国属性,如下所示:

public class STOCK : EntityBase
{
    public string TEXT {get;set;}
    public decimal AMOUNT {get;set;}

    private TAX SALESTAX {get;set;}
    [ForeignKey("SALESTAX")]
    public int SALESTAX_ID {get;set;}

    private TAX SPECIALTAX {get;set}
    [ForeignKey("SPECIALTAX")]
    public int SPECIALTAX_ID {get;set;}
}

public class TAX
{
    public int TAXCODE {get;set;}
    public string NAME {get;set;}
    
    public ICollection<STOCK> STOCK_TAX {get;set;}
    public ICollection<STOCK> STOCK_SPECIAL {get;set}
}

public class EntityBase, INotifyPropertyChanged
{
    public int ID {get;set;}
    //The interface is fully implemented here. Removed for brevity.
}

在我的 ViewModel 上,我同时注入了 StockDataServiceTaxDataService,如下所示:

public class StockDataService : IStockDataService
{
    private readonly MyDbContextFactory _factory;
    
    public StockDataService(MyDbContextFactory factory)
    {
        _factory = factory;
    }

    public async Task<STOCK> Create(STOCK entity)
    {
        using MyDbContext context = _factory.CreateDbContext();
        STOCK createdResult = await context.STOCKs.AddAsync(entity);
        return createdResult;
    } //Other CRUD methods are implemented as well, but removed for brevity.
}

public class TaxDataService : ITaxDataService
{
    private readonly MyDbContextFactory _factory;
    
    public TaxDataService(MyDbContextFactory factory)
    {
        _factory = factory;
    }

    public async Task<TAX> GetAll()
    {
        using MyDbContext context = _factory.CreateDbContext();
        return await context.TAXs.ToListAsync();
    }//Ditto
}

我的视图有一个组合框如下:

<ComboBox ItemsSource="{Binding TAXES}"
          SelectedValue="{Binding SALESTAX, Mode=TwoWay, UpdateSourceTrigger=Default}"  
          SelectedItem="{Binding SALESTAX}">
    <ComboBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel/>
        </ItemsPanelTemplate>
    </ComboBox.ItemsPanel>
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal" Margin="0">
                <Border Padding="0,0,2,0" BorderThickness="1" BorderBrush="LightGray">
                    <TextBlock>
                <Run Text="{Binding TAXCODE}"/>
                    </TextBlock>
                </Border>
                <Border Padding="2,0,0,0" BorderThickness="1" BorderBrush="LightGray">
                    <TextBlock>
                <Run Text="{Binding TEXT}"/>
                    </TextBlock>
                </Border>
            </StackPanel>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

我的viewModel如下:

public class StockCrudViewModel
{
    private readonly IStockDataService _stockDataService;
    private readonly ITaxDataService _taxDataService;
    public STOCK CurrentStock {get;set;}

    public ICollection<TAX> TAXES {get;set;}

    public StockCrudViewModel(IStockDataService stockDataService, ITaxDataService taxDataService)
    {
        _stockDataService = stockDataService;
        _taxDataService = taxDataService;

        FillTaxesList();
    }

    private async void FillTaxesList()
    {
        TAXES = new List<TAX>(await _taxDataService.GetAll());
    }

“保存”命令如下:

await _stockDataService.Create(_stockCrudViewModel.STOCK);
//Both the data service as well as the scoped view model are passed via dependency injection to the command.

编辑:我的DbContextHostBuilder 如下:

host.ConfigureServices((context, myServices) =>
     {
         string connString = context.Configuration.GetConnectionString("default");
         Action<DbContextOptionsBuilder> configureDbContext = c => { c.UseMySql(connString); c.EnableSensitiveDataLogging(); };
         myServices.AddSingleton<MyDbContextFactory>(new MyDbContextFactory(configureDbContext));
         myServices.AddDbContext<MyDbContext>(configureDbContext);
     });

现在,我明白为什么这样做会引发“尝试创建重复条目”异常,因为通过绑定设置 SALESTAX 属性将使用来自 MyDbContext 不同实例的 TAX,所以EF Core 无法相应地跟踪它。但是,由于我使用的是AddDbContext,并且传递了工厂,而不是上下文本身,因此每次调用数据服务方法之一时都会实例化一个新的上下文,我不确定如何让 EF Core 知道存在已经存在应该使用的 TAX 条目。

根据How to save an entity with a child entity which already exists in EF core?,他们建议使用用于创建新条目的相同上下文来获取现有条目。但是如果我的实体有五个或更多的外国财产,那不会影响业绩吗?还是我在规划 MVVM 架构时搞砸了?

【问题讨论】:

  • 如果对象设置了 ID,您可以在保存更改之前对其调用 DbContext.Update。顺便说一句,在命名约定方面,您无处不在。
  • 对不起,为了清楚起见,我只是将原始名称翻译成英文。

标签: c# wpf entity-framework mvvm


【解决方案1】:

还是我在规划 MVVM 架构时搞砸了?

是的。 MVVM 中 DbContext 的正确范围和生命周期位于 ViewModel 上。

这为 ViewModel 提供了一个 ChangeTracker 和 Unit Of Work,并使用 Local Data(一个 ObservableCollection)将数据绑定到加载的实体。

【讨论】:

  • 谁说这些服务不是单例的?即使在这种情况下,它们也很可能是。这是 WPF,所以请求的概念在这里不适用。
  • 糟糕,错过了 WPF。更新中。
  • 您将 DbContext 添加为 Scoped 服务,并将范围从 ViewModel 中挂起。然后将您的单例服务设为 Scoped 或 Transient。
  • DbContext 实例不是线程安全的,这意味着它们不能在单个范围内同时使用。
  • 顺便说一句,它确实有效。我所要做的就是注入服务提供者并在视图模型的 ctor 上请求一个新的范围。非常感谢!!!!
最近更新 更多