【问题标题】:return a list of data via stored proc to dapper通过存储过程将数据列表返回给 dapper
【发布时间】:2017-03-31 13:51:22
【问题描述】:

我正在尝试通过存储过程使用 Dapper 返回数据

我的 DTO 类类似于下面(为简洁起见删除了一些属性)

public class CarDTO
{
    public int CarID { get; set; }
    public string Manufacturer { get; set; }
    public List<CarOptionDTO> CarOptions { get; set; }
}

所以基本上在数据库中我有一个 CarOption 表,其中有一个 CarID 列 - 即 Car 可以有很多选项。

我当时的 DAL 层调用如下:

    private string getCarDataSp = "[dbo].[GetCarData]";

    public IEnumerable<CarDTO> GetCarData(int customerId, int year)
    {
        return Get(db => db.Query<CarDTO>(getCarDataSp , new { CustomerID = customerId, Year = year },
                                commandType: CommandType.StoredProcedure));
    }

Get 函数的实现在我的 BaseRepository 类中:

    public T Get<T>(Func<IDbConnection, T> query)
    {
        using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString))
        {
            return query.Invoke(db);
        }
    }

是否可以使用 Dapper 我也可以从存储的过程中返回 CarOptions?

我当时的存储过程如下:

ALTER PROCEDURE [dbo].[GetCarData]
    @CustomerID int, 
    @Year int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT * from [dbo].Car c
    JOIN [dbo].Customer cust ON c.CarID = cust.CarID
    WHERE cust.CustID = @CustomerID AND cust.Year = @Year

END

上面的查询可能会返回很多行以及 CarID 和制造商以及我为简洁而删除的其他属性。 Dapper 将按预期将这些映射回 DTO。

但是,如何在存储的过程中返回 CarOptions 列表 - 是否可以使用另一个查询或者是否应该以某种方式将其分离出来?例如,如果我返回 CarID 1 和 CarID 2,CarOption 表中可能有 6 行 CarID 1 和 CarOption 表中 CarID 2 4 行,理想情况下,我希望它们都返回到 CarOptions 集合如果可能,通过 Dapper?

【问题讨论】:

    标签: c# sql .net sql-server dapper


    【解决方案1】:

    是的……这是可能的。有几种方法可以使用 dapper 解决“一对多”场景:

    方法 1 - 返回两个查询,在 DAL 中合并

    ALTER PROCEDURE [dbo].[GetCarData]
        @CustomerID int, 
        @Year int
    AS
    BEGIN
        SET NOCOUNT ON;
    
        --return cars
        SELECT c.*
            from [dbo].Car c
        INNER JOIN [dbo].Customer cust ON c.CarID = cust.CarID
        WHERE cust.CustID = @CustomerID AND cust.Year = @Year
    
        --return options
        SELECT opt.*
            from [dbo].Car c
        INNER JOIN [dbo].Customer cust ON c.CarID = cust.CarID
        INNER JOIN dbo.CarOptions opt ON op.CarID = c.CarID
        WHERE cust.CustID = @CustomerID AND cust.Year = @Year
    
    END
    

    DAL

    var multi = db.QueryMultiple(getCarDataSp , new { CustomerID = customerId, Year = year },
                                    commandType: CommandType.StoredProcedure));
    
    var cars = multi.Read<CarDTO>();
    var options = multi.Read<CarOptionDTO>();
    
    //wire the options to the cars
    foreach(var car in cars){
        var carOptions = options.Where(w=>w.Car.CarID == car.CarID);        //I would override Equals in general so you can write w.Car.Equals(car)...do this on a common DataModel class
        car.Options = carOptions.ToList();
    }
    

    方法 2 - 返回一个查询,在 DAL 中拆分

    过程

    ALTER PROCEDURE [dbo].[GetCarData]
        @CustomerID int, 
        @Year int
    AS
    BEGIN
        SET NOCOUNT ON;
    
    
        SELECT c.*,  opt.*
         from [dbo].Car c
        INNER JOIN [dbo].Customer cust ON c.CarID = cust.CarID
        LEFT OUTER JOIN dbo.CarOptions opt ON op.CarID = c.CarID
        WHERE cust.CustID = @CustomerID AND cust.Year = @Year
    
    END
    

    DAL

    var tuples = db.Query<CarDTO, CarOptionDTO,Tuple<CarDTO,CarOptionDTO>>(getCarDataSp , new { CustomerID = customerId, Year = year },
    (car,opt)=> Tuple.Create(car,opt),                       commandType: CommandType.StoredProcedure);
    
    //group tuples by car
    var cars = tuple.GroupBy(gb=>gb.Item1.CarID)                    //again, overriding equals makes it so you can just to GroupBy(gb=>gb.Item1)
                .Select(s=>{
                var car = s.First().Item1;
                var carOptions = s.Select(t=>t.Item2).ToList()
    
                return car;
                });
    

    增强功能

    在查询中使用临时表

    这会将所有按参数过滤的内容放在一个查询中。后续查询是按 ID 进行的非常简单的选择。

    ALTER PROCEDURE [dbo].[GetCarData]
        @CustomerID int, 
        @Year int
    AS
    BEGIN
        SET NOCOUNT ON;
    
        declare @t table(CarID int);
    
        --filter cars (only deal with parameters here)
        INSERT INTO @t(CarID)
        SELECT c.CarID
        FROM dbo.Car c
            INNER JOIN [dbo].Customer cust ON c.CarID = cust.CarID
        WHERE cust.CustID = @CustomerID AND cust.Year = @Year
    
        --return cars
        SELECT c.*
        FROM [dbo].Car c
            INNER JOIN @t t ON t.CarID = c.CarID
    
        --return options
        SELECT opt.*
        FROM dbo.CarOptions opt
            INNER JOIN @t t ON t.CarID = opt.CarID
    
    END
    

    应用 BaseDTO 来帮助实现平等

    一旦您拥有 BaseDTO,并连接您的 ID,您就可以简单地说诸如:cars.Where(w=>w.Equals(car))、dictionary[car](如果它在其中)、if( car.Equals(otherCar)) 或 results.GroupBy(gb=>gb.Car)...

    public class BaseDTO
    {
        internal int ID { get; set; }
    
        /// <summary>
        /// If the obj is the same type with the same id we'll consider it equal.
        /// </summary>
        public override bool Equals(object obj)
        {
            if(obj == null || this.GetType() != obj.GetType())
            {
                return false;
            }
    
            return this.GetType().GetHashCode() == obj.GetType().GetHashCode() &&
                    this.ID == (BaseDTO)obj.ID;
        }
    
        /// <summary>
        /// If you override equals, you should override gethashcode.  
        /// http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode#263416
        /// </summary>
        public override int GetHashCode()
        {
            unchecked
            {
                int hash = 17;
    
                hash = hash * 23 + this.GetType().GetHashCode();
                hash = hash * 23 + this.ID;
    
                return hash;
            }
        }
    }
    
    public class CarDTO : BaseDTO
    {
        public int CarID
        {
            get { return this.ID; }
            set { this.ID = value; }
        }
        public string Manufacturer { get; set; }
        public List<CarOptionDTO> CarOptions { get; set; }
    }
    

    【讨论】:

    • 太好了,我会试一试。乍一看,我喜欢选项 2...在代码中保留尽可能多的逻辑并保持存储的过程“更干净”
    • 选项 2 的缺点是查询大小会更胖...如果汽车有 50 个选项,则该列数据重复 50 次(每个选项一次)。不是一个大问题,但无论如何。
    • 好点。它最多可以有 70 个选项...不太可能超过 10 个,但有可能它最多可以有 70 个。除了选项 1 有点太多之外,您是否可以看到任何缺点。SP 中的逻辑太多.我猜在 SP 中有逻辑的优点是如果需要修复就可以直接部署
    • 我认为您正在做的事情是查询逻辑而不是业务逻辑。查询逻辑(过滤、检索)在存储过程中是完全可以接受的。顺便说一句,它必须是一个sproc吗?如果直接传入 sql,维护起来会容易得多(也可以使用资源文件来帮助管理 sql 文件)。我们转换了数百个 proc,非常高兴。
    • 好的...第一种方法的另一个提示是简单地对一个临时表进行一次查询,该表存储您的汽车ID 并在一个地方考虑​​您的过滤(WHERE 子句)。然后,将两个非常干净的选择加入您刚刚放入临时的汽车 ID。基本上:1.)插入@t select CarID where @cust=xxx...2.)select * from Cars c inner join @tt ON t.CarID = c.CarID 3.)select * from CarOptions co inner join @ tt ON t.CarID = co.CarID。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-06
    • 2022-11-25
    • 2022-01-19
    • 2020-05-01
    • 1970-01-01
    • 2021-12-13
    • 2015-08-09
    相关资源
    最近更新 更多