我需要开发一个应用程序,对于不同的客户,需要将不同的现有遗留数据库作为持久性存储,或者也可以使用自己的独立数据库完全独立运行。
所以我的方法是:
我知道其中一个目标现有系统是CRM 2011系统。 “CRMRepository”实现最好使用Microsoft CRM SDK,而不是直接定位底层SQL数据库。
但是,CRM使用GUID作为其所有实体的主键,而其他数据库系统通常使用整数。
所以我对设计我的域实体的最佳方法感到困惑,以后在将其映射到存储库实现中的持久性存储时不会遇到问题。
典型的域实体将是
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
我是否应该将域实体更改为使用字符串作为主键属性,以便它可以容纳各种数据类型,然后在每个存储库实现中执行与正确数据类型的转换?还是有其他更好的方法来解决这个问题吗?
我想到的一件事就是将我的所有'Id'属性声明为类型对象,然后使用存储库模式和Automapper实现特定的实现并将我的域对象映射到例如EF实体(这将是然后在EFRepository实现中使用int类型的PK。
对于存储库的CRM实现,repo将使用CRM SDK并映射CRM内部使用的GUID上的“对象ID”。
这可能是一个可行的解决方案,还是太过分了?
由于
编辑1:我愿意接受商业解决方案。可以在这里使用LLBLGen吗?没有经验,但看起来它不允许你将相同的域定义重用到单独的“存储库”实现中,或者我错了吗?
编辑2:当前的解决方案结构概述,尝试遵循洋葱架构。不同的存储库实现将进入“基础架构”。然后,存储库实现将由客户通过DI“插入”。
答案 0 :(得分:1)
看起来你有两个选择。
将Id定义为一个对象,然后它与存储库使用的内容无关,它只是期望它与它保存的时间相同。这需要以低成本要求明确的演员表和处理失败。
创建int Id
和Guid GuidId
。这消除了显式强制转换的成本,并可能有助于类型安全。也意味着如果其他系统使用long
或string
作为ID,则会遇到问题。
答案 1 :(得分:0)
我有类似的情况。不同类型的公司有不同的信息,处理方式不同,并映射到较旧的遗留表,其中包含int和guid作为主键等。
我最终得到的是一个带有GUID主键和所有共享公共数据的基础(在EF中设置为抽象)表,然后我为每种类型都有一个继承表(这些表的主键是外键在基地)。在这些中,我能够声明有关该特定类型的任何特定信息,并将它们链接到其他旧表或新表。
在您的情况下,您可以拥有基本的客户类型,然后根据需要声明尽可能多的不同类型的客户,并在需要时扩展和链接它们。然后你可以这样定位它们:
var baseCust = db.Customers.FirstOrDefault(x => x.Id == someId);
if(baseCust is CustTypeA)
{
// Access extended properties and Legacy tables (via lazy loading)
var custA = (CustTypeA)baseCust;
custA.SomeExtendedProperty = blah;
var oldCompletedOrders = custA.Orders.Where(x => x.Completed).ToList();
//etc
这使我们能够灵活地前进并获取他们想要看到的所有旧垃圾。
答案 2 :(得分:0)
如何让我的所有域对象继承自抽象基类Entity,然后使用属性“public TKey Id {get; set;}”。或者甚至在每个类的基础上,如果我在不同的域对象上允许不同类型的键。这是一个可行的解决方案,请记住,存储库实现可以是从EF到NHibernate到CRM等自定义仓库的任何内容吗?
您可以实现一些接口和基本存储库(我已经阅读过类似else where的内容。)请考虑以下linqpad脚本(我仅将转储用于验证目的):
void Main()
{
var personList = new List<PersonSql>
{
new PersonSql
{
Id = 1,
FirstName = "Some",
LastName = "Person"
}
}.AsQueryable();
var repo = new PersonRepo();
repo.Query = personList;
repo.GetById(1).Dump();
}
// Define other methods and classes here
//entity base
class BaseEntity<TKey>
{
public TKey Id { get; set; }
}
//common constructs
class PersonBase<Tkey> : BaseEntity<Tkey>
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
//actual entity with type of int as the id
class PersonSql : PersonBase<int>
{
}
//and so on
class PersonNoSql : PersonBase<string>
{
}
//likley you would generate these or code them based on your data source when creating your mapping classes.
//repositories
interface IRepository<TEntity, TKeyI>
where TEntity : BaseEntity<TKeyI>
{
IQueryable<TEntity> Query {get; set;}
TEntity GetById(TKeyI key);
//Other Repository Methods here
}
abstract class RepoBase<TBaseEntity, TKeyB> : IRepository<TBaseEntity, TKeyB>
where TBaseEntity : BaseEntity<TKeyB>
{
//Base Implementations Here
public IQueryable<TBaseEntity> Query { get; set; }
public virtual TBaseEntity GetById(TKeyB key)
{
throw new NotImplementedException();
}
}
abstract class PersonRepoBase<TkeyType> : RepoBase<PersonBase<TkeyType>, TkeyType>
{
public override PersonBase<TkeyType> GetById(TkeyType key)
{
//Get a person.
throw new NotImplementedException();
}
}
//class PersonRepo : RepoBase<PersonNoSql, string>
//{
// public override PersonNoSql GetById(string id)
// {
// throw new NotImplementedException();
// }
//}
class PersonRepo : RepoBase<PersonSql, int>
{
public override PersonSql GetById(int id)
{
return Query.First(x => x.Id == id);
throw new NotImplementedException();
}
}
你通过这种方式获得了什么?好吧,它是一个存储库。它强烈地输入了Generic Constraints。我这样做的最初想法是有一个GetById的基本实现,但我的编译器不够智能,无法确定BaseRepo和BaseEntity之间的TKeyB是否与BaseRepo上的约束相同。所以它没有给我一个基础实现的好处。尽管如此,也许你可以尝试一下并根据自己的需要进行改进。
但是,它可能会像其他人建议的那样做得更好,并处理将Id作为主域上对象的特定模型。