如何在解耦数据层中设置主键

How to set primary key in decoupled datalayer

我想将 BL 与 DAL 分离。特别是我想保护主键(我不希望我的业务用户修改 Item.Id)。

在我的 DataLayer 中(假设它是一个简单的 sql table,带有自动生成的主键)我尝试插入项目并更新 item.Id。 我当然不能,因为它受到保护 属性。

有没有不用反射的方法? (我的意思是也许我的架构是错误的...)

业务层:

public class Item
{
    public int Id { get; protected set; }

    public string Name { get; set; }
}

public interface IRepository<Item>
{
    void Insert(Item item);

    //and other CRUD operations
}

DataLayer(使用 EntityFramework 和 sql 服务器):

public class EfRepository : IRepository<Item>
{
    EfContext ctx;     
    public void Insert(Item item)
    {      
        //EfContext uses its ItemEntity, so I have to map Item to EntityItem
        var mapped = AutoMapper.Map<Item, EntityItem>(item);
        ctx.Items.Add(mapped); 
        //after this operation EF will set primary key to mapped object
        //and now I need to set this to primary key to my business
        //domain object.
        item.Id = ?? // can't set protected property!!!
    }
}

您应该在数据库中完成此操作。如何执行此操作取决于您使用的数据库。

如果您需要通过代码专门设置一些其他 ID,我建议您专门为此添加另一个 属性,并让您的数据库仍然生成主键。

例如,您可以使用 Dapper 调用一个存储过程来执行您想要的插入(以及任何密钥生成)。

如我的评论所述

如果您正在使用 SQL 服务器,那么您可以在 Db 本身上设置它,无需再做任何事情

 create table test 
(
 Id int Identity(1,1) primary key ,
   Name varchar(100)
  )

您也可以将现有的 int 列也更改为标识。使用 alter table sql 命令

只要您使用 auto-generated 顺序整数作为主键,您就会遇到一些保护数据的问题。您必须将密钥从服务器传递到客户端,因此当用户更新数据时,您可以找到关联的记录。恶意用户只要改变主键的值,就可以使用不同的工具改变PK,访问不同的记录,因为很容易猜出其他记录的PK值。这些你当然知道。

此问题的行业标准解决方案是使用不易猜到的密钥(不是顺序整数值)。如果您使用 SQL 服务器作为数据库,您可以向 table 添加一个新列,类型定义为 GUID,并将其用作 PK。 GUID 值将由服务器自动分配,它不是顺序的,很难猜测其他值。我对其他数据库引擎不熟悉,但总之,解决方案是使用GUID。

以下是如何声明列并在 table 中添加行(从这篇 MSDN 文章中复制:https://technet.microsoft.com/en-us/library/ms190215(v=sql.105).aspx

CREATE TABLE MyUniqueTable
   (UniqueColumn   UNIQUEIDENTIFIER      DEFAULT NEWID(),
   Characters      VARCHAR(10) )
GO
INSERT INTO MyUniqueTable(Characters) VALUES ('abc')
INSERT INTO MyUniqueTable VALUES (NEWID(), 'def')

为了防止您的逻辑被意外修改Id,您可以使用"property injection pattern":

public class Item
{
    public Item() 
    { 
        _id = -1; 
    }

    public int Id 
    { 
        get 
        { 
            return _id; 
        }

        set
        {
             if (_id != -1)
             {
                 throw new OperationException("Item.Id has already been set");
             }

             _id = value;
        }
    }

    public string Name { get; set; }
}