我想扩展在这个线程上提出的问题
Binding listbox to observablecollection
通过赋予它持久化数据的能力。除了我安装了 Entity Framework Core,创建了一个 DbContext
类来保存记录之外,结构基本相同。我添加了一个按钮来将数据集保存到 SQL Server。我没有遇到编译错误,但是当我尝试将数据保存在数据库中时,我得到了这个运行时异常:
Message=实体类型“水果”需要定义主键。
下面列出了整个异常
System.InvalidOperationException 未处理 HResult=-2146233079 消息=实体类型“水果”需要定义主键。 Source=Microsoft.EntityFrameworkCore StackTrace: 在 Microsoft.EntityFrameworkCore.Internal.ModelValidator.ShowError(String message) 在 Microsoft.EntityFrameworkCore.Internal.ModelValidator.EnsureNonNullPrimaryKeys(IModel model) 在 Microsoft.EntityFrameworkCore.Internal.ModelValidator.Validate(IModel model) 在Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.<>c__DisplayClass14_0.b__0 中的 Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.<>c__DisplayClass14_0.b__0(对象 k ) 在 System.Collections.Concurrent.ConcurrentDictionary2.GetOrAdd(TKey key, Func2 valueFactory) 在 Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator) 在 Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()在 Microsoft.Ent ityFrameworkCore.Internal.LazyRef1.get_Value() 在 Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model() 在 Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServiceCollectionExtensions.<>c.
这是更新的水果类:
namespace Fruits.ViewModels
{
[Table("Fruits")]
public class Fruit : ViewModelBase
{
#region Constractor
public Fruit()
{
}
public Fruit(string name, String clrString)
{
FruitName = name;
// Parse colors like so: (Color)ColorConverter.ConvertFromString(clrString);
FruitColor = clrString;
_id = Guid.NewGuid();
}
public Fruit(string name, Color clr)
{
FruitName = name;
FruitColor = clr.ToString();
_id = Guid.NewGuid();
}
#endregion
#region Properties
private Guid _id;
[Key]
public Guid ID
{
get { return _id; }
}
#region FruitName
private string _fruitname;
public string FruitName
{
get
{
return _fruitname;
}
set
{
if (_fruitname != value)
{
_fruitname = value;
OnPropertyChanged("FruitName");
}
}
}
#endregion
#region FruitColor
private String _fruitcolor;
public String FruitColor
{
get
{
return _fruitcolor;
}
set
{
if (_fruitcolor != value)
{
_fruitcolor = value;
OnPropertyChanged("FruitColor");
}
}
}
#endregion
#region Selected Property
private bool _isSelected = true;
// NOTE: I renamed this property
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
#endregion
#endregion
}
}
更新的 MainWindows xaml(添加保存按钮)
<Window x:Class="Fruits.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Fruits"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<RoutedCommand x:Key="AddFruit" />
<RoutedCommand x:Key='SaveFruit' />
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command='{StaticResource AddFruit}'
Executed='AddFruitCommandBinding_Executed'
CanExecute='AddFruitCommandBinding_CanExecute' />
<CommandBinding Command='{StaticResource SaveFruit}'
Executed='SaveFruitCommandBinding_Executed'
CanExecute='SaveFruitCommandBinding_CanExecute' />
</Window.CommandBindings>
<Grid>
<StackPanel Orientation='Vertical'
Margin='10'>
<CheckBox IsChecked="{Binding ShowSelectedFruitOnly}">Selected Fruit Only</CheckBox>
<ListBox x:Name='MyList'
ItemsSource="{Binding FruitsView}"
ItemTemplate='{StaticResource FruitTemp}' />
<StackPanel Orientation="Horizontal"
Margin="0,10,0,0">
<Label Width="100">New Name:</Label>
<TextBox Width="200"
Text="{Binding NewFruitName, Mode=TwoWay }"
/>
</StackPanel>
<StackPanel Orientation="Horizontal"
Margin="0,10,0,0">
<Label Width="100">New Color:</Label>
<!--<TextBox Width="200"
Text="{Binding NewFruitColor, UpdateSourceTrigger=PropertyChanged}" />-->
<TextBox Width="200"
Text="{Binding NewFruitColor, Mode=TwoWay }" />
<ContentControl Style="{StaticResource ColorSwatch}"
Margin="2"
VerticalAlignment="Center"
Content="{Binding NewFruitColor}" />
</StackPanel>
<StackPanel Orientation='Horizontal'>
<Button x:Name='AddFruit'
Height='auto'
Width='auto'
Content='Add New Fruit 2'
Margin='0,10,0,0'
Command='{StaticResource AddFruit}' />
<Button x:Name='SaveFruit'
Height='auto'
Width='auto'
Content='Save Fruit'
Margin='100,10,0,0'
Command='{StaticResource SaveFruit}' />
</StackPanel>
</StackPanel>
</Grid>
</Window>
和我在主窗口后面的代码(添加的处理程序)
using Fruits.ViewModels;
using System;
using System.Windows;
using System.Windows.Input;
namespace Fruits
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
ViewModel.AddNewFruit("Jackfruit", "Yellow");
ViewModel.AddNewFruit("Watermelon", "ForestGreen");
ViewModel.AddNewFruit("Apple", "Red");
ViewModel.AddNewFruit("Banana", "Yellow");
ViewModel.AddNewFruit("Orange", "DeepSkyBlue");
//ViewModel.Fruits[0].IsSelected = false;
//ViewModel.Fruits[1].IsSelected = false;
ViewModel.FruitsView.Refresh();
}
public MainViewModel ViewModel { get { return DataContext as MainViewModel; } }
private void AddFruitCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
ViewModel.AddNewFruit();
}
private void AddFruitCommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute =
ViewModel != null
&& !String.IsNullOrWhiteSpace(ViewModel.NewFruitName)
&& !String.IsNullOrWhiteSpace(ViewModel.NewFruitColor)
;
}
private void SaveFruitCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
using (var db=new FruitDbContext())
{
db.SaveChanges();
}
}
private void SaveFruitCommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
}
}
我新添加的 dbContext:
namespace Fruits.ViewModels
{
public class FruitDbContext:DbContext
{
public DbSet<Fruit> Fruits { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionBuilder)
{
optionBuilder.UseSqlServer(@"Server = xxx; Database=Test; Integrated Security = True");
}
}
}
其他类保持不变,但我还是列出了它们:
视图模型库
namespace Fruits.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
}
ViewModel
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Media;
namespace Fruits.ViewModels
{
#region MainViewModel Class
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Fruits = new ObservableCollection<Fruit>();
}
public ICollectionView FruitsView { get; private set; }
#region ShowSelectedFruitOnly Property
private bool _showSelectedFruitOnly = true;
public bool ShowSelectedFruitOnly
{
get { return _showSelectedFruitOnly; }
set
{
if (value != _showSelectedFruitOnly)
{
_showSelectedFruitOnly = value;
FruitsView.Refresh();
OnPropertyChanged("ShowSelectedFruitOnly");
}
}
}
#endregion ShowSelectedFruitOnly Property
#region Add Methods
public void AddNewFruit()
{
Fruits.Add(new Fruit(NewFruitName, NewFruitColor));
NewFruitName = "";
NewFruitColor = "";
}
public void AddNewFruit(string name, string color)
{
Fruits.Add(new Fruit(name, color));
}
public void AddNewFruit(string name, Color color)
{
Fruits.Add(new Fruit(name, color));
}
#endregion Add Methods
#region NewFruitName Property
private String _newFruitName = default(String);
public String NewFruitName
{
get { return _newFruitName; }
set
{
if (value != _newFruitName)
{
_newFruitName = value;
OnPropertyChanged("NewFruitName");
}
}
}
#endregion NewFruitName Property
#region NewFruitColor Property
private String _newFruitColor = default(String);
public String NewFruitColor
{
get { return _newFruitColor; }
set
{
if (value != _newFruitColor)
{
_newFruitColor = value;
OnPropertyChanged("NewFruitColor");
}
}
}
#endregion NewFruitColor Property
#region Fruits Property
private static ObservableCollection<Fruit> _fruits;
public ObservableCollection<Fruit> Fruits
{
get { return _fruits; }
private set
{
if (value != _fruits)
{
_fruits = value;
FruitsView = CollectionViewSource.GetDefaultView(Fruits);
FruitsView.Filter = FruitFilterPredicate;
FruitsView.Refresh();
OnPropertyChanged("Fruits");
}
}
}
protected bool FruitFilterPredicate(Object o)
{
if (ShowSelectedFruitOnly)
{
return (o as Fruit).IsSelected;
}
return true;
}
#endregion Fruits Property
}
#endregion MainViewModel Class
}
应用程序.xaml
<Application x:Class="Fruits.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Fruits"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style x:Key="ColorSwatch"
TargetType="ContentControl">
<Setter Property="Width"
Value="24" />
<Setter Property="Height"
Value="24" />
<Setter Property="IsTabStop"
Value="false" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Stroke="Gray"
StrokeThickness="1">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding}" />
</Rectangle.Fill>
</Rectangle>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key='FruitTemp'>
<StackPanel Orientation='Horizontal'
Margin='5'>
<TextBlock x:Name='tbName'
Text='{Binding FruitName}'
Margin='10,0,0,0'
Width='100' />
<TextBlock x:Name='tbColor'
Text='{Binding FruitColor}'
Margin='10,0,0,0'
Width='100' />
<ContentControl Width="16"
Height="16"
Style="{StaticResource ColorSwatch}"
Content="{Binding FruitColor}" />
<!-- The problem here was you were trying to bind Checked, an event,
instead if IsChecked, a bool? property.
-->
<CheckBox x:Name='cbSelected'
Content='Selected'
Margin='10,0,0,0'
IsChecked='{Binding IsSelected}' />
</StackPanel>
</DataTemplate>
</Application.Resources>
</Application>
我的项目结构
https://i.stack.imgur.com/l3p5z.png
我在 SQL Server 中的表:
CREATE TABLE [dbo].[Fruits]
(
[ID] [uniqueidentifier] NOT NULL,
[FruitName] [nvarchar](50) NULL,
[FruitColor] [nvarchar](50) NULL,
[IsSelected] [nvarchar](1) NULL,
CONSTRAINT [PK_Fruit]
PRIMARY KEY CLUSTERED ([ID] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
请告知为什么消息说没有主键,而它确实存在
此异常消息并不意味着它需要在您的数据库中定义一个主键,它意味着它需要在您的类中定义一个主键。
尽管您已尝试这样做:
私人Guid _id; [密钥] 公共 Guid ID { 获取 { 返回 _id; } }
这没有任何效果,因为实体框架会忽略只读属性。它必须:当它从数据库中检索 Fruits
记录时,它会构造一个 Fruit
对象,然后为每个映射的属性调用属性设置器。这永远不会适用于只读属性。
您需要 Entity Framework 才能设置 ID
的值。这意味着该属性需要有一个设置器。
我带着类似的错误来到这里:
System.InvalidOperationException:'实体类型'MyType'需要定义一个主键。'
在阅读了 hvd 的回答后,我意识到我只是忘记将我的关键财产设为“公开”。这个..
namespace MyApp.Models.Schedule
{
public class MyType
{
[Key]
int Id { get; set; }
// ...
应该是这个。。
namespace MyApp.Models.Schedule
{
public class MyType
{
[Key]
public int Id { get; set; } // must be public!
// ...
我发现错误的原因有点不同。似乎 SQLite 想要使用正确的主键类属性名称。所以...
错误的PK名称
public class Client
{
public int SomeFieldName { get; set; } // It is the ID
...
}
正确的 PK 名称
public class Client
{
public int Id { get; set; } // It is the ID
...
}
public class Client
{
public int ClientId { get; set; } // It is the ID
...
}
仍然可以使用错误的 PK 名称,但我们必须使用 [Key] 属性,例如
public class Client
{
[Key]
public int SomeFieldName { get; set; } // It is the ID
...
}
这对我有用:
using System.ComponentModel.DataAnnotations;
[Key]
public int ID { get; set; }
确保您具备以下条件:
如果您的主键名称不是 Id 或 ID,请使用 [key]。使用公共关键字。主键应该有 getter 和 setter。
例子:
public class MyEntity {
[key]
public Guid Id {get; set;}
}
如果您收到错误消息:
实体类型“DeviceFlowCodes”需要定义一个主键。如果您打算使用无密钥实体类型,请在“OnModelCreating”中调用“HasNoKey”。有关无密钥实体类型的详细信息,请参阅 https://go.microsoft.com/fwlink/?linkid=2141943。
您可能正在使用具有个人用户帐户的解决方案,并且在 ApplicationDbContext
中使用了 protected override void OnModelCreating(ModelBuilder modelBuilder)
。
错误来自具有 public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }
的 ApiAuthorizationDbContext
。
通过调用 protected override void OnModelCreating
中的 base.OnModelCreating(modelBuilder);
解决错误。
资源:
https://github.com/IdentityServer/IdentityServer4/issues/3169
您的 Id 属性需要有一个设置器。但是,setter 可以是私有的。如果属性名为“Id”,则不需要 [Key]
属性,因为它将通过命名约定找到它,在该约定中查找名称为“Id”的键。
public Guid Id { get; } // Will not work
public Guid Id { get; set; } // Will work
public Guid Id { get; private set; } // Will also work
当我使用 Scaffold-DbContext 命令时,它没有在模型文件中包含“[key]”注释,也没有在“modelBuilder.Entity”块中包含“entity.HasKey(..)”条目。我的解决方案是在 *Context.cs 文件的每个“modelBuilder.Entity”块中添加这样的一行:
entity.HasKey(X => x.Id);
我并不是说这更好,甚至不是正确的方法。我只是说它对我有用。
另一个原因可能是您的实体类有几个名为 somhow /.*id/i
的属性 - 因此以 ID 不区分大小写和基本类型结尾并且没有 [Key]
属性。
EF 将通过查找以 ID 结尾的基本类型属性来尝试自行找出 PK。
看我的案例:
public class MyTest, IMustHaveTenant
{
public long Id { get; set; }
public int TenantId { get; set; }
[MaxLength(32)]
public virtual string Signum{ get; set; }
public virtual string ID { get; set; }
public virtual string ID_Other { get; set; }
}
不要问 - 遗留代码。 Id
甚至被继承,所以我不能使用 [Key]
(只是在这里简化代码)
但是这里 EF 完全糊涂了。
帮助的是在 DBContext 类中使用模型构建器。
modelBuilder.Entity<MyTest>(f =>
{
f.HasKey(e => e.Id);
f.HasIndex(e => new { e.TenantId });
f.HasIndex(e => new { e.TenantId, e.ID_Other });
});
PK 的索引是隐含的。
我碰巧什么都试过了:
确保该属性有一个设置器
尝试使用 [Key] 注释
使用了一个名为“Id”的属性,带有和不带有 [Key] 注释。
这些都不起作用,原因很简单:我的数据库不可用。
修复与数据库的连接解决了“需要主键”问题。
(奇怪的是即使在这种情况下也会显示这个错误。)
实体类型“DisplayFormatAttribute”需要定义一个主键。
就我而言,我发现问题在于我使用了这样的属性:
public string LastName { get; set; } //OK
public string Address { get; set; } //OK
public string State { get; set; } //OK
public int? Zip { get; set; } //OK
public EmailAddressAttribute Email { get; set; } // NOT OK
public PhoneAttribute PhoneNumber { get; set; } // NOT OK
不确定是否有更好的方法来解决它,但我将电子邮件和电话号码属性更改为字符串。问题解决了。
在我从实体中删除 HasNoKey() 方法之前,没有一个答案有效。不要忘记从您的数据上下文中删除它,否则 [Key] 属性将无法解决任何问题。
使用 Scaffold-DbContext 删除并添加回表中,错误消失了
在 DBContext 我有这个 <List>
代码导致错误:
public DbSet<List<<Item>> Items { get; set; } = null!;
您可以通过创建另一个类来解决它,我创建了一个包含 List<Item>
的类 ListItem
,然后声明两者:
public DbSet<Item> Items { get; set; } = null!;
public DbSet<ListItem> ListItems { get; set; } = null!;
如果您使用的是 FluentApi,请确保您正在应用您的配置类,如下所示:
internal class MyModelConfiguration : IEntityTypeConfiguration<MyModel>
{
public void Configure(EntityTypeBuilder<MyModel> builder)
{
builder.ToView("MyView", "dbo");
...
protected void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new MyConfiguration());
...
DbSet<Fruit>.Local
应该是您可以绑定到的ObservableCollection<Fruit>
。它跟踪加载到上下文中的所有实体。然后(或之前),只需确保将实体加载到上下文 (context.Fruits.Load();
) 中,它应该可以工作。这要求您将上下文至少与您的视图模型一样长。对于上下文应该是短命的还是长命的,意见不一。就个人而言,在这种情况下,我会选择长期存在的上下文。ObservableCollection<Fruit>
,不是吗?您正在您的问题中显示的代码中执行此操作。因此,除了创建全新的ObservableCollection<Fruit>
对象之外,您需要做的就是使用 EF 为您创建的对象。