将域模型映射到具有不同主键数据类型的不同持久性存储

时间:2014-01-27 19:27:56

标签: c# entity-framework ef-code-first dynamics-crm-2011

我需要开发一个应用程序,对于不同的客户,需要将不同的现有遗留数据库作为持久性存储,或者也可以使用自己的独立数据库完全独立运行。

所以我的方法是:

  • 独立于最终持久性存储开发域模型
  • 如果需要,使用EF Code-First存储库实现将其映射到自己的独立数据库
  • 如果需要,使用其他存储库实现将其映射到旧数据库系统

我知道其中一个目标现有系统是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; }
}
  • 对于独立解决方案:使用基于代码优先的EF存储库,没问题
  • 使用整数作为主键定位现有数据库:将其映射到正确的主键属性,没问题
  • 但是,如何定位CRM后端? “人”实体通常会映射到CRM中的“帐户”实体,但是,与所有其他CRM实体一样,PK是accountid,这是一个GUID。

我是否应该将域实体更改为使用字符串作为主键属性,以便它可以容纳各种数据类型,然后在每个存储库实现中执行与正确数据类型的转换?还是有其他更好的方法来解决这个问题吗?

我想到的一件事就是将我的所有'Id'属性声明为类型对象,然后使用存储库模式和Automapper实现特定的实现并将我的域对象映射到例如EF实体(这将是然后在EFRepository实现中使用int类型的PK。

对于存储库的CRM实现,repo将使用CRM SDK并映射CRM内部使用的GUID上的“对象ID”。

这可能是一个可行的解决方案,还是太过分了?

由于

编辑1:我愿意接受商业解决方案。可以在这里使用LLBLGen吗?没有经验,但看起来它不允许你将相同的域定义重用到单独的“存储库”实现中,或者我错了吗?

编辑2:当前的解决方案结构概述,尝试遵循洋葱架构。不同的存储库实现将进入“基础架构”。然后,存储库实现将由客户通过DI“插入”。

Solution structure, trying to follow onion architecture

3 个答案:

答案 0 :(得分:1)

看起来你有两个选择。

  1. 将Id定义为一个对象,然后它与存储库使用的内容无关,它只是期望它与它保存的时间相同。这需要以低成本要求明确的演员表和处理失败。

  2. 创建int IdGuid GuidId。这消除了显式强制转换的成本,并可能有助于类型安全。也意味着如果其他系统使用longstring作为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作为主域上对象的特定模型。