2017-03-31 33 views
2

我想通过存储过程返回采用精致小巧的数据通过存储过程返回数据的列表,以短小精悍

我的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)); 
    } 

我获取功能的实现是在我BaseRepository类为:

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

采用小巧玲珑的,我可以从返回是否有可能我的存储过程也是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和4行与CarID 2的CarOption表中,理想情况下,我希望将它们全部返回到CarOptions集合如果可能,通过Dapper?

回答

1

是的......这是可能的。有一对夫妇的解决 “一到多” 的场景与短小精悍的方式:

方法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

Proc

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.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; } 
} 
+0

很好,我会给这个去吧。从第一眼我就喜欢选项2 ...尽量保持代码中的逻辑并保持存储过程'更清洁' –

+1

选项2的缺点是查询大小会更加丰富...如果汽车有50个选项该列数据重复50次(每个选项一次)。不是一个大问题,但无论如何。 – BlackjacketMack

+0

好点。它可能有多达70个选项......不大可能有10个以上的选项,但可能有多达70个。除了有一点选项1之外,你可以看到有什么缺点。除SP之外还有很多逻辑。我猜想SP中的逻辑具有如果需要修复时可以直接部署的优势 –