在使用nhibernate时,更新子集合最优雅的方式是什么(不创建不必要的添加和删除)?
本文关键字:创建 是什么 不必要 添加 删除 方式 nhibernate 更新 子集合 | 更新日期: 2023-09-27 18:09:21
我有一个名为Project的域对象,它映射到我的SQL server数据库中的一个表。它有一个名为Dependencies的List属性。
public class Project
{
public int Id;
public List<ProjectDependency> Dependencies;
}
public class ProjectDependency
{
public Project Project;
public Project Dependency;
}
和我试图找出最有效的方法来更新依赖列表给定一个新的dependencyid列表。
这是一个简单的实现:
public void UpdateDependencies(Project p, List<int> newDependencyIds)
{
p.Dependencies.Clear();
foreach (var dependencyId in newDependencyIds)
{
Project d = GetDependency(dependencyId)
p.Dependencies.Add(new ProjectDependency{Project = p, Dependency = d});
}
}
但是这里的问题是,即使没有任何更改,我也会清除所有项并在之前存在的相同项上进行插入。
我正在寻找一种优雅的方式来确定diff(添加了什么,删除了什么),只是做出这些改变,所以如果一个依赖关系之前和之后,那么它不会被触及。
public void UpdateDependencies(Project p, List<int> newDependencyIds)
{
p.Dependencies.RemoveAll(d => !newDependencyIds.Contains(d.Dependency.Id));
var toAdd = newDependencyIds.Select(d => p.Dependencies.Any(pd => pd.Dependency.Id != d)).ToList();
toAdd.ForEach(dependencyId => p.Dependencies.Add(new ProjectDependency{Project = p, Dependency = GetDependency(dependencyId)}));
}
我对你的Model
课程真的很困惑。您正在创建一个类Project
。
public class Project
{
public int Id;
...
}
本身在另一个类中引用-
public class ProjectDependency
{
public Project Project;
public Project Dependency;
}
而你又在被引用的类中引用了那个引用类?-
public class Project
{
public int Id;
public List<ProjectDependency> Dependencies; //that is a cycle and it is very bad architecture
}
你到底为什么要做那件事?是不是太频繁了?
解决方案:
(编辑)我建议一个更简单的模型——
public class Project
{
public int Id;
public List<ProjectDependency> Dependencies;
}
public class ProjectDependency
{
//..... other property
public Project DependentProject;
}
就是这样。那是我的班。为什么?因为NHbernate
会自动创建所需的外键。由于您正在使用ORM,我认为ORM创建多少表和哪些列来支持您并不重要,这是ORM的头痛,而不是用户。但是,如果您愿意,您仍然可以使用ManyToMany
引用覆盖它,以便使用单独的表。
现在,在类定义固定之后,我会用这样的东西来更新我的依赖-
public class Project
{
public int Id;
public List<ProjectDependency> Dependencies;
public List<ProjectDependency> AddDepedency(List<ProjectDependency> dependencies){
dependencies.ForEach(d => {
if (Dependencies.All(x=> x.Id != d.Id)){
Dependencies.Add(d);
}
});
return Dependencies;
}
public List<ProjectDependency> RemoveDepedency(List<ProjectDependency> dependencies){
dependencies.ForEach(d => {
if (Dependencies.Any(x=> x.Id == d.Id)){
Dependencies.Remove(d);
}
});
return Dependencies;
}
public List<ProjectDependency> UpdateDependency(List<ProjectDependency> dependencies){
dependencies.ForEach(d => {
if (Dependencies.All(x=> x.Id != d.Id)){
Dependencies.Add(d);
}
});
Dependencies.RemoveAll(d => depdencies.All(x => x.Id != d.Id));
return Depdendencies;
}
}
注意事项-
不是发送
List<int>
,而是发送List<ProjectDependency>
,这是因为Model
不应该知道系统中从数据库中获取的服务,因此他们只会担心类。无论当前列表中有什么,
UpdateDependency
都会将其替换为提供的列表。所以这些项迟早会从db中获取。因此,最好将它们预先设置为ProjectDependency
对象。我添加了两个额外的方法,添加和删除,这是我要做的实际方式。但是由于你使用的是
UpdateDependency
,我也添加了这个还要注意,我在每个函数中返回列表,这将有助于在与其他服务和类一起使用时嵌套方法。它可以帮助您每次使用那些需要链接的方法时减少一行代码。
最后,但并非最不重要的是,我没有测试代码。所以你可能会发现一些拼写或语法错误。如果您喜欢代码,那么请更改或修复错误(如果有的话)并使用它。
下面是一个控制台应用程序,它将定位未更改,添加和删除的ProjectDependency id。获取id更容易,因为List<int>
被传递给UpdateDependencies方法。希望对你有帮助。
class Program
{
static void Main(string[] args)
{
var project = CreateProject();
var newDependencyIds = new List<int>() {2, 3, 4, 13};
UpdateDependencies(project, newDependencyIds);
}
private static Project CreateProject()
{
var project = new Project() {Id = 1};
project.Dependencies = new List<ProjectDependency>();
for (int projectId = 2; projectId < 10; projectId++)
{
var dependency = new ProjectDependency() {Project = project, Dependency = new Project() {Id = projectId}};
project.Dependencies.Add(dependency);
}
return project;
}
private static void UpdateDependencies(Project p, List<int> newDependencyIds)
{
var oldDependencyIds = p.Dependencies.Select(d => d.Dependency.Id);
var unchanged = oldDependencyIds.Intersect(newDependencyIds);
var added = newDependencyIds.Except(oldDependencyIds);
var removed = oldDependencyIds.Except(newDependencyIds);
Console.WriteLine("Old ProjectDependency Ids: " + string.Join(", ", oldDependencyIds));
Console.WriteLine("New ProjectDependency Ids: " + string.Join(", ", newDependencyIds));
Console.WriteLine();
Console.WriteLine("Unchanged: " + string.Join(", ", unchanged));
Console.WriteLine("Added: " + string.Join(", ", added));
Console.WriteLine("Removed: " + string.Join(", ", removed));
Console.WriteLine();
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
输出为:
Old ProjectDependency Ids: 2, 3, 4, 5, 6, 7, 8, 9
New ProjectDependency Ids: 2, 3, 4, 13
Unchanged: 2, 3, 4
Added: 13
Removed: 5, 6, 7, 8, 9
Press any key to continue...
请以此为基本思路。
在您的情况下,最好使用HashSet<>
进行实际收集,ICollection<T>
进行声明。当从数据库中检索时,NHibernate将使用ISet<>
版本<= 3.3和HashSet版本>= 4.0(目前在alpha中)。
然后在你的ProjectDependency
中,你应该实现GetHashCode
和Equals
覆盖,以正确识别相同的项目是否已经存在于集合中。
这样,Contains
和所有Linq
操作应该能够检测到重复项。
public class Project
{
public virtual int Id { get; set; }
public virtual ICollection<ProjectDependency> Dependencies { get; set; }
public Project()
{
this.Dependencies = new HashSet<ProjectDependency>();
}
}
public class ProjectDependency
{
public Project Project;
public Project Dependency;
// This is a simplified version and don't check for nulls in internal members
// or transient objects
public override int GetHashCode()
{
return this.Project.Id + this.Dependency.Id;
}
// This is a simplified version and don't check for nulls in internal members
// or transient objects
public override bool Equals(object obj)
{
var dep = obj as ProjectDependency;
if (dep == null)
{
return false;
}
return this.Project.Id == dep.Project.Id && this.Dependency.Id == dep.Dependency.Id;
}
}
现在,您的更新方法可以简化为:
public void UpdateDependencies(Project p, List<int> newDependencyIds)
{
var newDependencies = newDependencyIds.Select(d => new ProjectDependency{ Project = p, Dependency = GetDependency(d) });
var addDependencies = newDependencies.Except(p.Dependencies);
var delDependencies = p.Dependencies.Except(newDependencies);
foreach (var dependency in addDependencies)
{
p.Dependencies.Add(dependency);
}
foreach (var dependency in delDependencies)
{
p.Dependencies.Remove(dependency);
}
}
现在它将只更新修改过的项。
更新:添加了@doan-van-tuan对这个答案的建议。
UPDATE 2:如果您的ProjectDependency
类没有任何其他属性,并且仅用作多对多类,请考虑以下内容。
在这种情况下,可以删除ProjectDependency
类,如下所示:
public class Project
{
// This attribute is used to ensure GetHashCode always return the same value
private int? hashCode;
public virtual int Id { get; set; }
public virtual ICollection<Project> Dependencies { get; set; }
public Project()
{
this.Dependencies = new HashSet<Project>();
}
// This is a simplified but correct implementation of GetHashCode
public override int GetHashCode()
{
if (this.hashCode.HasValue)
{
return this.hashCode.Value;
}
if (this.Id == 0)
{
return (this.hashCode = base.GetHashCode()).Value;
}
return (this.hashCode = typeof(Project).GetHashCode() * this.Id * 251).Value;
}
public override bool Equals(object obj)
{
var p = obj as Project;
if (Object.ReferenceEquals(p, null))
{
return false;
}
return p.Id == this.Id;
}
}
public class ProjectMapping : ClassMapping<Project>
{
public ProjectMapping()
{
this.Table("Project");
this.Id(x => x.Id, mapper => mapper.Generator(Generators.Assigned));
this.Set(x => x.Dependencies,
mapper =>
{
mapper.Table("ProjectDependency");
mapper.Key(m => m.Column("ProjectId"));
},
mapper =>
{
mapper.ManyToMany(m => m.Column("DependencyId"));
});
}
}
你的更新方法可以是:
public void UpdateDependencies(Project p, IEnumerable<Project> newDependencies)
{
var addDependencies = newDependencies.Except(p.Dependencies);
var delDependencies = p.Dependencies.Except(newDependencies);
foreach (var dependency in addDependencies)
{
p.Dependencies.Add(dependency);
}
foreach (var dependency in delDependencies)
{
p.Dependencies.Remove(dependency);
}
}
你是如何得出项目有依赖关系的?您只声明您有一个表,所以我假设实际上没有名为ProjectDependency的表。
然而,如果有这样一个表,它可能是多余的。如果只有Project表,那就更容易了。听我说:如果是我,如果我能够修改表,我会让Project有一个自引用(递归)外键。这样,项目表就会有一个名为ParentProjectId的列,该列的类型与表项目的主键相同,然后将主键作为外键约束引用。
类似这样的东西(这是ms-sql服务器,但概念应该站在其他数据库,如果不让我知道):
CREATE TABLE [dbo].[Project](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
[ParentProjectId] [int] NULL,
CONSTRAINT [PK_Project] 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]
GO
ALTER TABLE [dbo].[Project] WITH CHECK ADD CONSTRAINT [FK_ParentProject_Project] FOREIGN KEY([ParentProjectId])
REFERENCES [dbo].[Project] ([Id])
GO
ALTER TABLE [dbo].[Project] CHECK CONSTRAINT [FK_ParentProject_Project]
GO
如果可能的话,我将按如下方式建模:
public class Project
{
private IList<Project> _dependencies;
public virtual int? Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Project> Dependencies
{
get { return _dependencies ?? (_dependencies = new List<Project>()); }
set { _dependencies = value; }
}
public virtual Project ParentProject { get; set; }
public virtual Project AddDependency(Project dependency)
{
dependency.ParentProject = this;
Dependencies.Add(dependency);
return this;
}
}
那么在映射中我将像这样映射(现在是伪代码,将更新):
public sealed class ProjectMap : ClassMap<Project>
{
public ProjectMap()
{
Id(x => x.Id).Column("Id").GeneratedBy.Native();
Map(x => x.Name);
References(x => x.ParentProject).Column("ParentProjectId").Cascade.All();
HasMany(x => x.Dependencies).KeyColumn("ParentProjectId").Inverse().Cascade.AllDeleteOrphan();
}
}
这基本上使您能够使用递归无限嵌套项目。
现在,根据您的问题,您正在使用nhibernate,并且您的Project实体有一个int类型的主键。通常我会让这个键也成为一个标识列,我上面的映射让nhibernate知道数据库将生成它。
只是基于你的帖子问"最优雅的方式",如果是我,基于你的模型,假设我有能力/权限使用数据库模式,这就是我将如何做到的。
AddDependency方法的工作方式是,它处理将依赖项连接到项目,以及当您实际执行Session时。保存在项目实体上,NHibernate实际上只会插入那些新项目。我会简单地创建或选择新的项目,并传递给他们,而不是试图与id的工作,只是让数据库处理(因为它很擅长)
我可能不会尝试做多对多,但如果这是你真正需要让我知道,我会更新我的答案。我只是按照你说的在我的SQL server数据库中有一个表hth