C# 实体框架4 TPH继承,如何将一种类型更改为另一种类型?
我已经找到了一些关于这方面的信息,但还不足以让我理解这个场景的最佳实践是什么。我有一个抽象基类“Firm”的典型TPH设置。我有几个孩子“小商行”、“大商行”等继承自商行。实际上,我对公司有不同的实际分类,但在这个例子中,我尽量保持简单。在TPH数据库中,我有一个单独的FirmTypeId列(int)的firmTable,它区分所有这些类型。一切都很好,除了我有一个要求,允许用户改变一种类型的公司到另一种。例如,用户可能在添加公司时出错,并希望将其从大公司更改为小公司。因为实体框架不允许将区分数据库列作为属性公开,所以我认为没有办法通过EF将一种类型更改为另一种类型。如果我错了,请纠正我。在我看来,我有两个选择:C# 实体框架4 TPH继承,如何将一种类型更改为另一种类型?,c#,asp.net,asp.net-mvc,entity-framework,entity-framework-4,C#,Asp.net,Asp.net Mvc,Entity Framework,Entity Framework 4,我已经找到了一些关于这方面的信息,但还不足以让我理解这个场景的最佳实践是什么。我有一个抽象基类“Firm”的典型TPH设置。我有几个孩子“小商行”、“大商行”等继承自商行。实际上,我对公司有不同的实际分类,但在这个例子中,我尽量保持简单。在TPH数据库中,我有一个单独的FirmTypeId列(int)的firmTable,它区分所有这些类型。一切都很好,除了我有一个要求,允许用户改变一种类型的公司到另一种。例如,用户可能在添加公司时出错,并希望将其从大公司更改为小公司。因为实体框架不允许将区分数
我看过一篇文章,人们认为OOP的原则之一是实例不能改变它们的类型。虽然这对我来说很有意义,但我似乎无法将这些点联系起来。如果我们遵循这条规则,那么使用任何类型的继承(TPH/TPT)的唯一时间就是确定一种类型永远不会转换为另一种类型。所以小公司永远不会变成大公司。我看到一些建议,认为应该用作文来代替。即使这对我来说没有意义(这意味着我看不出一家公司如何拥有一家大公司,对我来说,一家大公司就是一家公司),但如果数据在多个表中,我可以看到如何在EF中建模组合。但是,在数据库中只有一个表的情况下,它似乎是TPH或我在上面的第1和第2部分中描述的内容。是的,您完全正确。EF继承不支持此方案。为现有公司更改公司类型的最佳方法是使用存储过程 有关更多信息,请查看此帖子:
除非您明确希望使用关系继承的多态功能,否则为什么不考虑使用拆分策略呢
我在我们的项目中遇到了这个问题,我们有core
DBContext
和一些具有自己的DBContext
的“可插入”模块,其中“模块用户”继承了“核心(基本)用户”。希望这是可以理解的
我们还需要将用户
更改为客户
(如果需要,还需要同时更改为另一个“继承的”用户
,以便用户可以使用所有这些模块
正因为如此,我们尝试使用TPT继承,而不是TPH——但TPH也会以某种方式起作用
一种方法是使用许多人建议的自定义存储过程
我想到的另一种方法是向DB发送自定义插入/更新查询
private static bool UserToCustomer(User u, Customer c)
{
try
{
string sqlcommand = "INSERT INTO [dbo].[Customers] ([Id], [Email]) VALUES (" + u.Id + ", '" + c.Email + "')";
var sqlconn = new SqlConnection(ConfigurationManager.ConnectionStrings["DBContext"].ConnectionString);
sqlconn.Open();
var sql = new SqlCommand(sqlcommand, sqlconn);
var rows = sql.ExecuteNonQuery();
sqlconn.Close();
return rows == 1;
}
catch (Exception)
{
return false;
}
}
在此场景中,客户
继承用户
,并且只有字符串电子邮件
使用TPH时,查询只会从INSERT…VALUES…
更改为UPDATE…SET…,其中[Id]=…
不要忘记更改鉴别器列。
在下一次调用dbcontext.Users.OfType
之后,我们的原始用户“转换”为客户
底线:我还尝试了另一个问题的解决方案,其中包括从
ObjectStateManager
中分离原始实体(用户)并修改新实体(客户)状态,然后保存dbcontext.SaveChanges()
。这对我不起作用(TPH和TPT都不起作用)。这可能是因为每个模块使用单独的DBContexts,或者是因为EntityFramework 6(.1)忽略了这一点。
编辑:抱歉,这是EF 6.x的答案 为了完整性,我发布了示例代码。在这个场景中,我有一个基本的
Thing
类。然后,子类:ActiveThing
和DeletedThing
我的ODataThingsController
,有一个主GetThings
,我只想公开ActiveThing
s,但它正在GetThing(ThingId)仍然可以返回任意类型的对象。Delete
操作执行从ActiveThing
到DeletedThing
的转换,转换方式与OP要求的方式相同,也与其他答案中描述的方式相同。我使用的是内联SQL(参数化)
公共类myDbModel:DbContext
{
public myDbModel():base(“name=ThingDb”){}
公共DbSet Things{get;set;}//db table
public DbSet ActiveThings{get;set;}//现在我的ThingsController'GetThings'从这个
模型创建时受保护的覆盖无效(DbModelBuilder modelBuilder)
{
//TPH(每个层次结构的表):
modelBuilder.Entity()
.Map(thg=>thg.Requires(“鉴别器”).HasValue(“A”))
.Map(thg=>thg.Requires(“鉴别器”).HasValue(“D”);
}
}
这是我更新的东西controller.cs
public class ThingsController : ODataController
{
private myDbModel db = new myDbModel();
/// <summary>
/// Only exposes ActiveThings (not DeletedThings)
/// </summary>
/// <returns></returns>
[EnableQuery]
public IQueryable<Thing> GetThings()
{
return db.ActiveThings;
}
public async Task<IHttpActionResult> Delete([FromODataUri] long key)
{
using (var context = new myDbModel())
{
using (var transaction = context.Database.BeginTransaction())
{
Thing thing = await db.Things.FindAsync(key);
if (thing == null || thing is DeletedThing) // love the simple expressiveness here
{
return NotFound();//was already deleted previously, so return NotFound status code
}
//soft delete: converts ActiveThing to DeletedThing via direct query to DB
context.Database.ExecuteSqlCommand(
"UPDATE Things SET Discriminator='D', DeletedOn=@NowDate WHERE Id=@ThingId",
new SqlParameter("@ThingId", key),
new SqlParameter("@NowDate", DateTimeOffset.Now)
);
context.ThingTransactionHistory.Add(new Ross.Biz.ThingStatusLocation.ThingTransactionHistory
{
ThingId = thing.Id,
TransactionTime = DateTimeOffset.Now,
TransactionCode = "DEL",
UpdateUser = User.Identity.Name,
UpdateValue = "MARKED DELETED"
});
context.SaveChanges();
transaction.Commit();
}
}
return StatusCode(HttpStatusCode.NoContent);
}
}
公共类ThingsController:ODataController
{
private myDbModel db=new myDbModel();
///
///仅公开ActiveThings(而不是DeletedThings)
///
///
[启用查询]
公共IQueryable GetThings()
{
返回db.ActiveThings;
}
公共异步任务删除([FromODataUri]长键)
{
使用(var context=new myDbModel())
{
使用(var transaction=context.Database.BeginTransaction())
{
等待
public class ThingsController : ODataController
{
private myDbModel db = new myDbModel();
/// <summary>
/// Only exposes ActiveThings (not DeletedThings)
/// </summary>
/// <returns></returns>
[EnableQuery]
public IQueryable<Thing> GetThings()
{
return db.ActiveThings;
}
public async Task<IHttpActionResult> Delete([FromODataUri] long key)
{
using (var context = new myDbModel())
{
using (var transaction = context.Database.BeginTransaction())
{
Thing thing = await db.Things.FindAsync(key);
if (thing == null || thing is DeletedThing) // love the simple expressiveness here
{
return NotFound();//was already deleted previously, so return NotFound status code
}
//soft delete: converts ActiveThing to DeletedThing via direct query to DB
context.Database.ExecuteSqlCommand(
"UPDATE Things SET Discriminator='D', DeletedOn=@NowDate WHERE Id=@ThingId",
new SqlParameter("@ThingId", key),
new SqlParameter("@NowDate", DateTimeOffset.Now)
);
context.ThingTransactionHistory.Add(new Ross.Biz.ThingStatusLocation.ThingTransactionHistory
{
ThingId = thing.Id,
TransactionTime = DateTimeOffset.Now,
TransactionCode = "DEL",
UpdateUser = User.Identity.Name,
UpdateValue = "MARKED DELETED"
});
context.SaveChanges();
transaction.Commit();
}
}
return StatusCode(HttpStatusCode.NoContent);
}
}