C# 实体框架委托实体从外部源添加/加载
我正在尝试修改现有的实体框架类,以便从服务而不是基于config键的数据库进行读/写 几周内,我们希望将数据保存在服务和现有数据库中,稍后验证数据一致性,然后完全切换到服务 这是我心目中的方法:C# 实体框架委托实体从外部源添加/加载,c#,.net,entity-framework,entity-framework-6,C#,.net,Entity Framework,Entity Framework 6,我正在尝试修改现有的实体框架类,以便从服务而不是基于config键的数据库进行读/写 几周内,我们希望将数据保存在服务和现有数据库中,稍后验证数据一致性,然后完全切换到服务 这是我心目中的方法: public class UserDbContext : IdentityDbContext<AspNetUsers> { public DbSet<Address> Addresses { get; set; } public DbSet<Person&g
public class UserDbContext : IdentityDbContext<AspNetUsers>
{
public DbSet<Address> Addresses { get; set; }
public DbSet<Person> Persons { get; set; }
}
public class AspNetUsers : IdentityUser
{
public virtual ICollection<Person> Persons { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public AspNetUsers()
{
Addresses = new List<Address>();
Persons = new List<Person>();
}
}
但是我们已经将地址部分移动到了单独的服务中,希望在现有代码中使用它,而不是从数据库中读取
class Program
{
static void Main(string[] args)
{
var context = new UserDbContext();
var address = new AddressProvider().GetAddress("user12345"); // Load
var newAddress = new Address("street", "city");
new AddressProvider().SaveAddress(newAddress);
// How to delegate the Address from database to service here?
var user = context.Users.FirstOrDefault(x => x.UserId == "user12345");
user.Addresses.FirstOrDefault();
user.Addresses.Add(newAddress);
}
}
public class AddressProvider
{
public Address GetAddress(string userId, UserDbContext context)
{
bool loadFromService = true; // will be from config
if (loadFromService)
{
return new Address(); // this will be fetch from api via HttpClient
}
return context.Addresses.FirstOrDefault(x => x.UserId == userId);
}
public void SaveAddress(Address address, UserDbContext context)
{
bool loadFromService = true; // will be from config
if (loadFromService)
{
SaveAddress(address); // this will be sent to API
return;
}
context.Addresses.Add(newAddress);
context.SaveChanges();
}
}
现在的问题是,当通过导航属性加载此地址时,我们无法直接使用该服务
有没有一种方法可以使用任何实体框架功能来委派此任务?因此,无论何时调用Address对象,它都将被委托给其他类来加载实体而不是数据库?在对DbContext执行数据查询时,实体要么映射到当前数据库,要么映射到当前数据库。这张照片上没有灰色区域。你所问的和我的相似。在本例中,它不是另一个DbContext,但问题是相同的 当您针对DbContext进行查询时,服务该查询涉及以下高级步骤:(除非该查询针对缓存的结果集)
DbModelBuilder
配置中表示的映射,将表达式转换为SQL命令.Include
s),因为它将无法找到它们。对于这一点,obsoletateAttribute
是一个很好的工具,我们可以通过警告跟踪代码中的任何引用,同时仍然允许在转换状态下编译旧代码。这也是对任何新代码的提示,不要使用这个遗留引用
尝试并静默地将此条件功能注入DbContext可能是可行的,但这并不实际。混合方法的最佳解决方案是将新的AddressProvider
实现并排“栓接”
过了一段时间,当您删除了以前对旧地址集的所有引用和查询,并且希望删除旧地址表时,只需将其从UserDbContext
中删除即可,请确保在映射中也忽略它
只需将新的提供者实现添加到DbContext
,这样您就不必复制对每个调用的引用
public class UserDbContext : IdentityDbContext<AspNetUsers>
{
#region Disposable AddressProvider
AddressProvider AddressProvider { get; private set; } = new AddressProvider(this);
/// <summary>
/// Dispose managed members such as AddressProvider, otherwise they will keep this context active
/// </summary>
protected override void Dispose(bool disposing)
{
if(this.AddressProvider != null)
{
try
{
this.AddressProvider.Dispose();
this.AddressProvider = null;
}
catch (Exception) { /*Ignore errors during dispose*/ }
}
base.Dispose(disposing);
}
#endregion Disposable AddressProvider
public DbSet<Address> Addresses { get; set; }
public DbSet<Person> Persons { get; set; }
}
public class AddressProvider : IDisposable
{
UserDbContext _userContext;
public readonly bool LoadFromService;
public readonly bool UpdateDatabase;
public AddressProvider(UserDbContext dbContext, bool loadFromService = true, bool updateDatabase = true)
{
_userContext = dbContext;
LoadFromService = loadFromService;
UpdateDatabase = updateDatabase;
}
#region IDiposable - Release the dbContext reference
public void Dispose()
{
_userContext = null;
}
#endregion IDiposable - Release the dbContext reference
public Address GetAddress(string userId)
{
if (LoadFromService)
{
return GetAddressFromAPI(userId);
}
return _userContext.Addresses.FirstOrDefault(x => x.UserId == userId);
}
public void SetAddress(Address address)
{
// To support Hybrid operations, we can update both the service and the database
if (LoadFromService)
{
SaveAddressInAPI(address);
}
if (UpdateDatabase)
{
var userId = address.UserId;
// It's probably simpler to delete any existing address for this user
foreach(var a in context.Addresses.Where(x => x.UserId == userId).ToList())
context.Entry(fund).State = System.Data.Entity.EntityState.Deleted;
_userContext.Addresses.Add(address);
_userContext.SaveChanges();
}
}
public void GetAddressFromAPI(string userId)
{
// this will be fetch from api via HttpClient
return new Address { UserId = userId };
//throw new NotImplementedException();
}
public void SaveAddressInAPI(Address address)
{
// this will be sent to API
//throw new NotImplementedException();
}
}
public class AspNetUsers : IdentityUser
{
public virtual ICollection<Person> Persons { get; set; }
[Obsolete("Stop using the Addresses collection directly, see 'GetAddress(UserDbContext)'")]
public virtual ICollection<Address> Addresses { get; set; }
public AspNetUsers()
{
Addresses = new List<Address>();
Persons = new List<Person>();
}
}
public static class AddressExtensions
{
public static Address GetAddress(this AspNetUsers user, UserDbContext context)
{
return context.AddressProvider.GetAddress(user.UserId);
}
public void SetAddress(this AspNetUsers user, Address address, UserDbContext context)
{
// ensure the UserId is set to this user
address.UserId = user.UserId;
context.AddressProvider.SetAddress(address);
}
}
我不建议这样做,但您也可以添加一个LoadAddresses
方法来管理条件加载逻辑。我不喜欢这样,因为它允许太多的代码像EF上下文的一部分一样运行,而事实上它不是,但是如果没有它,答案就不完整
public static class MoreAddressExtensions
{
public static Address LoadAddress(this AspNetUsers user, UserDbContext context)
{
if (context.AddressProvider.LoadFromService)
{
if (this.Addresses == null)
this.Addresses = new HashSet<Address>();
else
this.Addresses.Clear();
var externalAddress = context.AddressProvider.GetAddress(user.UserId));
if (externalAddress != null)
this.Addresses.Add(externalAddress);
}
else
this.Addesses = context.Addresses.Where(x => x.UserId == userId).ToList();
}
}
稍后,当您要从上下文amke中删除表时,请确保从上下文中删除public DbSet Addresses{get;set;}
,并忽略模型配置中的类型:
modelBuilder.Ignore<Address>();
因此,您希望禁用地址提供程序,或者您实现了该切换,因为您认为这样会更容易?从
UserDbContext
模型中完全删除地址,并将其转换为User
/Person
上的方法,以便通过提供程序获取或更新地址,要简单得多。@chrischaller,是的,但这取决于配置。我们希望在两周内使用现有数据库,并在两个位置写入数据并验证数据一致性,然后再完全切换到使用该服务,只是为了确认,AspNetUsers
在DbContext(数据库)中有一个地址集合但是在服务中,对于每个AspNetUsers
(用户)实例,只有一个地址
?
public static class MoreAddressExtensions
{
public static Address LoadAddress(this AspNetUsers user, UserDbContext context)
{
if (context.AddressProvider.LoadFromService)
{
if (this.Addresses == null)
this.Addresses = new HashSet<Address>();
else
this.Addresses.Clear();
var externalAddress = context.AddressProvider.GetAddress(user.UserId));
if (externalAddress != null)
this.Addresses.Add(externalAddress);
}
else
this.Addesses = context.Addresses.Where(x => x.UserId == userId).ToList();
}
}
static void Main(string[] args)
{
var context = new UserDbContext();
var user = context.Users.FirstOrDefault(x => x.UserId == "user12345");
// load the address
user.LoadAddress(context);
var address = user.Addresses.FirstOrDefault();
var newAddress = new Address("street", "city");
user.SetAddress(newAddress, context);
}
modelBuilder.Ignore<Address>();
/// <summary> detect changes to Address Entities and redirect them through the provider </summary>
public override int SaveChanges()
{
foreach (var entry in this.ChangeTracker.Entries())
{
if (entry.Entity is Address a)
{
switch(entry.State)
{
case EntityState.Modified:
case EntityState.Added:
{
if (AddressProvider.LoadFromService)
{
if (String.IsNullOrEmpty(a.UserId))
a.UserId = a.User.Id;
AddressProvider.SaveAddressInAPI(a);
}
if (!AddressProvider.UpdateDatabase)
entry.State = EntityState.Unchanged;
break;
}
case EntityState.Deleted:
{
// no mention of how to handle delete, so we'll add a blank one
if (AddressProvider.LoadFromService)
{
string userId = a.UserId;
if (String.IsNullOrEmpty(a.UserId))
userId = a.User.Id;
AddressProvider.SaveAddressInAPI(new Address { UserId = userId });
}
if (!AddressProvider.UpdateDatabase)
entry.State = EntityState.Unchanged;
break;
}
case EntityState.Unchanged:
default:
continue;
}
}
}
return base.SaveChanges();
}