我正在开发一个网络应用程序,我需要两个不同版本的数据:"正在进行中"用户可以随意更改的版本以及"已批准的"版本
我已经看到了一些关于创建历史跟踪的资源,但就我而言,我不希望数据被隐藏起来'直到有人要求历史时才使用这两个"已批准"和"正在进行中"版本将处于活动状态(虽然批准将是只读的)
类结构看起来像这样(简化为简洁):
public class VideoGame
{
[Key]
public Guid ID { get; set; }
public string Name { get; set; }
public ICollection<Monster> Monsters { get; set; }
public ICollection<Weapon> Weapons { get; set; }
}
public class Monster
{
public Guid ID { get; set; }
public string Name { get; set; }
public int xOrigin { get; set; }
public int yOrigin { get; set; }
public string SpriteFile { get; set; }
}
public class Weapon
{
public Guid ID { get; set; }
public string Name { get; set; }
public int PowerLevel { get; set; }
}
所以场景是用户A创建一个VideoGame,命名为#34; Death!&#34;然后附加可怕的怪物和核武器。管理员&#34;批准&#34;它,所以在某些表格中,例如&#34; VideoGame_Approved&#34;,&#34; Monster_Approved&#34;等,这些数据按原样存在。
同时,用户B进入并编辑用户A的游戏。用户B重新命名它&#34; Unicorn Adventures,&#34;并附上彩虹怪物和魔法武器。只有在此修订获得批准后,它才会覆盖“死亡!”#34;在批准的部分。
用户A&#34;死亡&#34;游戏将不断访问,可能通过单独的Web服务和其他数据库级操作。它绝对应该作为SQL表存在。
数据库是代码优先的,没有任何限制。以上&#34; _approved&#34;使用SQL触发器很容易解决问题,但是我希望实体框架能够提供一些我不知道的更优雅。
任何有关此问题的有效解决方案的帮助或提示都会很棒。
编辑:使用@ SteveGreen的评论,我想出了两个解决方案。这是未经测试的,是一种速记娱乐:
public class VideoGame
{
public Guid ID { get; set; }
public string Name { get; set; }
public GameVersion Version {get;set;}
public ICollection<Monster> Monsters { get; set; }
}
public class GameVersion
{
[Key]
public Guid ID { get; set; }
public Guid GameID { get; set; }
//Metadata (dates, approvers, etc.)
}
public class MVCController
{
public void Approve()
{
var viewModel = new VideoGame(); //This is the viewModel or raw data from some source
using (ProjectContext context = new ProjectContext())
{
//this creates a copy of the video game that isn't attached to the context.
var newVersion = context
.VideoGames
.AsNoTracking()
.Include("Monsters")
.Where(v => v.ID == viewModel.ID)
.FirstOrDefault();
newVersion.ID = Guid.NewGuid();
newVersion.Version = new GameVersion();
newVersion.Version.GameID = newVersion.ID;
foreach (var m in newVersion.Monsters) { m.ID = Guid.NewGuid() };
//repeat for weapons
context.VideoGames.Add(newVersion);
context.SaveChanges();
//later on, we can do an extension method to do
var game = context.Versions.GetLatestVersion(ourGame.ID);
//do stuff with game
}
}
}
解决方案二:
public class BaseGame
{
public Guid ID { get; set; }
public string Name { get; set; }
public ICollection<Monster> Monsters { get; set; }
}
public class UnapprovedVideoGame : BaseGame
{
//additional properties
}
public class ApprovedVideoGame : BaseGame
{
//additional properties
}
public class ProjectContext : System.Data.Entity.DbContext
{
public System.Data.Entity.DbSet<UnapprovedVideoGame> UnapprovedGames;
public System.Data.Entity.DbSet<ApprovedVideoGame> ApprovedGames;
}
答案 0 :(得分:1)
一种选择是简单地使用两个表,就像你的&#34; X_approved&#34;战略。但是,不是使用触发器,只需创建一个新实体并将其保存在匹配表中。例如:
public class GameContext : DbContext
{
public DbSet<VideoGame> WorkingVideoGames { get; set; }
public DbSet<VideoGame> VideoGames { get; set; }
public DbSet<Monster> WorkingMonsters { get; set; }
public DbSet<Monster> Monsters { get; set; }
public DbSet<Weapon> WorkingWeapons { get; set; }
public DbSet<Weapon> Weapons { get; set; }
}
对于&#34; live&#34;将始终访问数据。 DbSets(例如武器),当你需要原始数据,或者反对&#34;工作&#34;当您需要正在进行的数据时,DbSet。然后,您将要使用域服务来处理移动它们。只要您的数据更新仅通过服务进行,那么您可以通过不公开直接更新它们的方法来强制执行已批准数据的只读性质。
以下示例是一个非常简单的部分实现,展示了如何同时执行工作保存和批准。
public class GameService
{
IDbContextFactory<GameContext> factory; //injected in constructor
public async Task SaveNewVideoGame(IVideoGameSaveRequest request) //Defined elsewhere
{
var vg = new VideoGame(request);
var monsters = request.Monsters.Select(m=>new Monster(m)).ToList();
var weapons = request.Weapons.Select(w=>new Weapon(w)).ToList();
using (GameContext context = factory.Create())
{
context.WorkingVideoGames.Add(vg);
context.WorkingWeapons.AddRange(weapons);
context.WorkingMonsters.AddRange(monsters);
await context.SaveChangesAsync();
}
}
public async Task ApproveVideoGame(IVideoGameApprovalRequest request) //Defined elsewhere
{
using (GameContext context = factory.Create())
{
VideoGame vg = await context.WorkingVideoGames.FirstOrDefaultAsync(v=>v.ID == request.VideoGameID);
// These three uses of a .Clone() method indicate creation of a simple method which will
// return a new entity of the same type with the same property values -- but only a shallow copy
List<Monster> monsters = vg.Monsters.ToList().Select(m => m.Clone()).ToList();
List<Weapon> weapons = vg.Weapons.ToList().Select(w => w.Clone()).ToList();
vg = vg.Clone();
vg.Monsters.AddRange(monsters);
vg.Weapons.AddRange(weapons);
context.VideoGames.Add(vg);
context.Weapons.AddRange(weapons);
context.Monsters.AddRange(monsters);
await context.SaveChangesAsync();
}
}
}
(请注意,上面的代码未经测试,因此可能无法反映完整的API。)