使用 MVVM 创建具有现有外部属性的 EF Core 实体

Posted

技术标签:

【中文标题】使用 MVVM 创建具有现有外部属性的 EF Core 实体【英文标题】:Creating an EF Core entity with an existing foreign property with MVVM 【发布时间】:2021-05-26 02:55:35 【问题描述】:

我将 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。顺便说一句,在命名约定方面,您无处不在。 对不起,为了清楚起见,我只是将原始名称翻译成英文。 【参考方案1】:

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

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

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

【讨论】:

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

以上是关于使用 MVVM 创建具有现有外部属性的 EF Core 实体的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ef core 对具有原始表名和属性的数据库进行反向工程

EF 6 是不是要求外键具有反映外表名称的名称?

使用外键发布新实体会导致创建另一个外部实体而不是引用现有实体

具有自动属性名称实现的 MVVM INotifyPropertyChanged

使用 EF6 Code First 的 MySQL 现有数据库 - 使用脚手架创建控制器会导致错误

如何将 MVVM 与 EF 一起使用(在 SQLite 中使用非常复杂的数据库)?