Skip to content

soul-soft/Dapper.Linq

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 

Repository files navigation

Dapper.Linq

免责说明:使用前请先测试和阅读源代码并自觉遵守开源协议

新版本的linq支持

  1. SqlBatis.Extensions.Dapper

3.x版本说明

  1. 移除dapper,无需依赖dapper,内置一个简约的对象映射器,用于替代dapper
  2. 新增简单的java.ibatis的xml功能
  3. 新版文档参考:新版参考文档,建议通过单测和源码学习
  4. 建议尽快从Dapper.Linq和Dapper.Common移植到SqlBatis sqlBatis代码可维护性极好,实体映射规则可定制化,移植成本非常小。
  5. 免责说明:使用前先测试

About author

  1. Email:1448376744@qq.com
  2. QQ:1448376744
  3. QQGroup:642555086
  4. Document

Config

 //"LinqTypeMap" file in unit test project 
 Dapper.SqlMapper.TypeMapProvider = (type) => new LinqTypeMap(type);
 DbContextFactory.AddDataSource(new DataSource()
 {
     Default = true,
     Name = "mysql",
     ConnectionFacotry = () => new MySql.Data.MySqlClient.MySqlConnection("server=localhost;user id=root;password=1024;database=test;"),
     DatasourceType = DatasourceType.MYSQL,
     UseProxy = true//use static proxy,for logger
 });

Insert

IDbContext context = null;
try
{
    context = DbContextFactory.GetDbContext();
    //because set "id[isIdentity=true]",so not set "id" value
    var row1 = context.From<Student>().Insert(new Student()
    {
        Grade = Grade.A,
        CreateTime = DateTime.Now,
        Name = "jack",
    });
    //batch added
    var row2 = context.From<Student>().Insert(new List<Student>()
    {
        new Student()
        {
            Grade = Grade.C,
            CreateTime = DateTime.Now,
            Name = "tom",
        },
         new Student()
        {
            Grade = Grade.F,
            CreateTime = DateTime.Now,
            Name = "jar",
        },
    });
}
catch (Exception e)
{
    //debug sql logger
    Console.WriteLine(context.Loggers);
}
finally
{
    context.Close();
}

Update

using (var context = DbContextFactory.GetDbContext())
{
    //param
    var age = 20;
    DateTime? time = null;
    var sid = 1;

    //subquery
    var subquery = new SubQuery<School>()
        .Where(a => a.Id == sid)
        .Select(s => s.Name);

    var row1 = context.From<Student>()
        .Set(a => a.Age, a => a.Age + age)
        .Set(a => a.Name, subquery)
        .Set(a => a.CreateTime, time, time != null)
        .Where(a => a.Id == 16)
        .Update();

    //function
    context.From<Student>()
        .Set(a => a.Name, a => MysqlFun.REPLACE(a.Name, "a", "b"))
        .Where(a => a.Id == 14)
        .Update();  

    //lock
    var student = context.From<Student>()
        .Where(a => a.Id == 16)
        .Single();
        
    var row2 = context.From<Student>()
        .Set(a => a.Age, 80)
        .Set(a => a.Version, Guid.NewGuid().ToString())
        .Where(a => a.Id == 16 && a.Version == student.Version)
        .Update();

    //entity update by primary key
    var row3 = context.From<Student>()
        .Filter(a => a.SchoolId)
        .Update(new Student()
        {
            Id = 2,
            CreateTime = DateTime.Now
        });
     //reset update where
     var row3 = context.From<Student>()
        .Where(a => a.Id = 2 && a.Version=oldVersion)
        .Update(new Student()
        {            
            Id = 2,
            Version=Guid.NewGuid().ToString(),
            CreateTime = DateTime.Now
        });

Delete

using (var context = DbContextFactory.GetDbContext())
{
    var row1 = context.From<Student>()
         .Where(a => a.Id == 16)
         .Delete();

    var subquery = new SubQuery<School>()
        .Where(a => a.Id >= 0)
        .Select(a => a.Id);

    var row2 = context.From<Student>()
         .Where(a => Operator.In(a.Id, subquery))
         .Delete();
}

Transaction

  IDbContext dbContext = null;
  try
  {
      dbContext = DbContextFactory.GetDbContext();
      dbContext.Open(true);
      dbContext.From<Student>().Insert(new Student()
      {
          Name="stduent1"
      });
      //throw new Exception("rollback");
      dbContext.From<School>().Insert(new School()
      {
          Name = "school1"
      });
      dbContext.Commit();
  }
  catch (Exception)
  {
      dbContext?.Rollback();
      throw;
  }
  finally
  {
      dbContext?.Close();
  }

Anonymous

// Custom Mapper Handles the Problem that Anonymous Types Can't Match Constructors
//Copy "DefaultTypeMap" from "dapper" and modify this method
  public ConstructorInfo FindConstructor(string[] names, Type[] types)
        {
            var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            foreach (ConstructorInfo ctor in constructors.OrderBy(c => c.IsPublic ? 0 : (c.IsPrivate ? 2 : 1)).ThenBy(c => c.GetParameters().Length))
            {
                ParameterInfo[] ctorParameters = ctor.GetParameters();
                if (ctorParameters.Length == 0)
                    return ctor;

                if (ctorParameters.Length != types.Length)
                    continue;

                int i = 0;
                for (; i < ctorParameters.Length; i++)
                {
                    if (!string.Equals(ctorParameters[i].Name, names[i], StringComparison.OrdinalIgnoreCase))
                        break;
                    if (types[i] == typeof(byte[]) && ctorParameters[i].ParameterType.FullName == "System.Data.Linq.Binary")
                        continue;
                    var unboxedType = Nullable.GetUnderlyingType(ctorParameters[i].ParameterType) ?? ctorParameters[i].ParameterType;
                    //if ((unboxedType != types[i] && !SqlMapper.HasTypeHandler(unboxedType))
                    //    && !(unboxedType.IsEnum && Enum.GetUnderlyingType(unboxedType) == types[i])
                    //    && !(unboxedType == typeof(char) && types[i] == typeof(string))
                    //    && !(unboxedType.IsEnum && types[i] == typeof(string)))
                    //{
                    //    break;
                    //}
                }

                if (i == ctorParameters.Length)
                    return ctor;
            }

            return null;
        }
  
 SqlMapper.TypeMapProvider = (type) => new LinqTypeMap();

Select

 //single
 var student = context.From<Student>()
     .Where(a => a.Id == 19)
     .Single();

 //subquery
 var id = 0;
 var age = 50;
 var subquery = new SubQuery<School>()
    .Where(a => a.Id >= id)
    .Select(a => a.Id);

 //Verify that subquery parameters are written to the current query
 var students2 = context.From<Student>()
     .OrderBy(a => a.Age)
     .Where(a => a.Id >= Operator.Any(subquery) && a.Age > age)
     .Select();

 //Partial columns
 var students3 = context.From<Student>()
    .Select(s => new
    {
        s.Id,
        s.Age
    });

Group by

  var students = context.From<Student>()
      .GroupBy(a => a.Age)
      .Having(a => MysqlFun.Count(1L) > 2)
      .Select(s => new
      {
          Count = MysqlFun.Count(1L),
          s.Age,
      });

Dynamic query

var param = new Student()
{
    Name = "zs",
    Grade = Grade.B,
    SchoolId = null,
    Id = null,
    Type = 5
};

//Multiple Where Connections with AND
var students = context.From<Student>()
    .Where(a => a.Id == param.Id, param.Id != null)
    .Where(a => Operator.Contains(a.Name, param.Name), param.Name != null)
    .Where(a => a.Grade == param.Grade, param.Grade != null)
    .Where(a => a.Id > 2 || a.Age < 80, param.Type == 5)
    .Select();


var students2 = context.From<Student>()
    .Where(a => a.Id == param.Id, param.Id != null)
    .Where(a => a.Grade == param.Grade, param.Grade != null)
    .Where(a => Operator.StartsWith(a.Name, param.Name), param.Name != null)
    .Where(a => a.Id > 2 || a.Age > 20, param.Type == 8)
    .Select();

Task page

 var students = context.From<Student>()
     .Page(1, 10, out long total)
     .Select();

Join

 var students = context.From<Student, School>()
     .Join((a, b) => a.SchoolId == b.Id)
     .Select((a, b) => new
     {
         a.Id,
         StuName = a.Name,
         SchName = b.Name
     });

Other query

//limit 0,10
var students1 = context.From<Student>()
    .Take(10)
    .Select();

//limit 10,20 
var students2 = context.From<Student>()
   .Skip(10, 20)
   .Select();

//Calling functions in expressions is not recommended, but n-tier attribute access is supported
var student3 = context.From<Student>()
    .Where(a => a.CreateTime == DateTime.Now.Date)
    .Select();
//lock
var students4 = context.From<Student>()
   .With(LockType.FOR_UPADTE)
   .Select();    
   
//exists1
var flag1 = context.From<Student>()
    .Where(a => a.Id > 50)
    .Exists();

//exists2
var subquery = new SubQuery<School>()
    .Where(a => a.Id >= 2)
    .Select(a => a.Id);
var flag2 = context.From<Student>()
    .Where(a => Operator.Exists(subquery))
    .Count();

//count
var count = context.From<Student>()
   .Where(a => a.Id > 50)
   .Count();

//sum
var sum = context.From<Student>()
 .Where(a => a.Id > 50)
 .Sum(s => s.Id * s.Age);

//distinct
var disinct = context.From<Student>()
    .Distinct()
    .Select(s => s.Name);

Custom Function

  • step1
 public static class MysqlFun
 {
     [Function]
     public static string REPLACE(string column,string oldstr,string newstr)
     {
         return string.Empty;
     }
     [Function]
     public static T Count<T>(T column)
     {
         return default;
     }
   
 }
  • step2
 var students = context.From<Student>()
     .GroupBy(a => a.Age)
     .Having(a => MysqlFun.Count(1L) > 2)
     .Select(s => new
     {
         Count = MysqlFun.Count(1L),
         s.Age,
     });

Expression To Sql

var prefix = "@";
var values = new Dictionary<string, object>();
Expression<Func<Student,bool>> expression = s => s.Age > 40; 
var expression = ExpressionUtil.BuildExpression(expression, values, prefix);

Object to Sql

DEMO.1 Case When Then Else

step1: implement

//Dapper.common doesn't care how you implement it, it only concerns the result of build.
public class Case<T> : ISqlBuilder
{
    private List<Expression> _whens = new List<Expression>();
    private List<string> _thens = new List<string>();
    string _else = null;
    public string Build(Dictionary<string, object> values, string prefix)
    {
        var sb = new StringBuilder();
        foreach (var item in _whens)
        {
            var express = ExpressionUtil.BuildExpression(item, values, prefix);
            sb.AppendFormat(" WHEN {0} THEN '{1}'", express, _thens[_whens.IndexOf(item)]);
        }
        if (_else != null)
        {
            sb.AppendFormat(" ELSE '{0}'", _else);
        }
        return string.Format("(CASE {0} END)", sb);
    }
    public static implicit operator string(Case<T> d) => string.Empty;
    public Case<T> When(Expression<Func<T, bool>> expression)
    {
        new Dictionary<string, object>();
        _whens.Add(expression);
        return this;
    }
    public Case<T> Then(string value)
    {
        _thens.Add(value);
        return this;
    }
    public Case<T> Else(string value)
    {
        _else = value;
        return this;
    }
}

step2: use

//case
var caseWhen = new Case<Student>()
    .When(a => a.Age <= 18)
    .Then("children")
    .When(a => a.Age <= 40)
    .Then("Youth")
    .Else("Old");

//The "caseWhen" object is still an ISqlBuild instance at run time, not a string
//The engine passes in parameters and calls the "caseWhen.Build" method of the instance
var students1 = context.From<Student>()
    .Where(a => caseWhen == "Old" || caseWhen == "Youth")
    .Select(s => new
    {
        s.Id,
        GroupAge = (string)caseWhen
    });

DEMO.2 Complex Function

step1: implement

 public class DateAdd<T> : ISqlBuilder
 {
     public string Column { get; set; }
     public int Expr { get; set; }
     public string Unit { get; set; }
     public Dictionary<string, object> Values { get; set; }

     public string Build(Dictionary<string, object> values, string prefix)
     {
         return "DATE_ADD(" + Column + ",INTERVAL " + Expr + " " + Unit + ")";
     }
     public DateAdd(Expression<Func<T, DateTime?>> column, int expr, string unit)
     {
         this.Column = ExpressionUtil.BuildColumn(column, null, null).FirstOrDefault().Value;
         this.Expr = expr;
         this.Unit = unit;
     }
     public static bool operator <(DateTime? t1, DateAdd<T> t2)
     {
         return false;
     }
     public static bool operator <(DateAdd<T> t1, DateTime? t2)
     {
         return false;
     }
     public static bool operator >(DateTime? t1, DateAdd<T> t2)
     {
         return false;
     }
     public static bool operator >(DateAdd<T> t1, DateTime? t2)
     {
         return false;
     }
     public static explicit operator DateTime(DateAdd<T> d) => DateTime.Now;
 }

step2: use

 var adddayfun = new DateAdd<Student>(a => a.CreateTime, 1, "day");

 //in columus
 var student1 = context.From<Student>()
     .Select(s => new
     {
         s.Id,
         DateTime = (DateTime)adddayfun //just for type inference
     });

 //in expression
 var student2 = context.From<Student>()
     .Where(a => adddayfun > DateTime.Now)
     .Select();

DEMO.3 Window Function

step1: implement

 public class WinFun<T> : ISqlBuilder
 {
     string _partition { get; set; }
     string _orderby { get; set; }
     private string _methodName { get; set; }
     public WinFun<T> ROW_NUMBER()
     {
         _methodName = nameof(ROW_NUMBER);
         return this;
     }
     public WinFun<T> PARTITION<TResult>(Expression<Func<T, TResult>> columns)
     {
         var cls = ExpressionUtil.BuildColumns(columns, null, null);
         _partition += string.Join(",", cls.Select(s => s.Value));
         return this;
     }
     public WinFun<T> ORDERBY<TResult>(Expression<Func<T, TResult>> columns, bool asc = true)
     {
         var cls = ExpressionUtil.BuildColumns(columns, null, null);
         _orderby += string.Join(",", cls.Select(s => s.Value));
         _orderby += !asc ? "DESC" : "ASC";
         return this;
     }
     /*If there are no parameters in the expression, there is no need to build in build-method*/
     public string Build(Dictionary<string, object> values, string prefix)
     {
         if (_methodName == nameof(ROW_NUMBER))
         {
             return string.Format("ROW_NUMBER()OVER(ORDER BY {0})", _orderby);
         }
         throw new NotImplementedException();
     }

     public static implicit operator ulong(WinFun<T> d) => 0;
 }

step2: use

 var winFun = new WinFun<Student>()
     .ORDERBY(a => a.Age)
     .ROW_NUMBER();

 var student1 = context.From<Student>()
    .Select(s => new
    {
        s.Id,
        s.Name,
        s.Age,
        RowNum = (ulong)winFun
    });
    

DEMO.4 Subquery

step1: implement

public class SubQuery<T> : ISubQuery where T : class
{
    private Expression _where { get; set; }
    private Expression _column { get; set; }
    private string _method { get; set; }
    private bool _useSignTable = true;
    public string Build(Dictionary<string, object> values, string prefix)
    {
        var table = EntityUtil.GetTable<T>();
        var column = ExpressionUtil.BuildColumn(_column, values, prefix).SingleOrDefault().Value;
        var where = ExpressionUtil.BuildExpression(_where, values, prefix, _useSignTable);
        if (_method == nameof(this.Select))
        {
            return string.Format("(select {0} from {1} where {2})", column, table.TableName, where);
        }
        if (_method == nameof(this.Count))
        {
            return string.Format("(select count({0}) from {1} where {2})", column, table.TableName, where);
        }
        throw new NotImplementedException();
    }
    public SubQuery<T> Where(Expression<Func<T, bool>> expression)
    {
        _where = expression;
        return this;
    }
    public SubQuery<T> Where<T1, T2>(Expression<Func<T1, T2, bool>> expression)
    {
        _useSignTable = false;
        _where = expression;
        return this;
    }
    public SubQuery<T> Select<TResut>(Expression<Func<T, TResut>> expression)
    {
        _method = nameof(this.Select);
        _column = expression;
        return this;
    }
    public SubQuery<T> Count<TResut>(Expression<Func<T, TResut>> expression)
    {
        _method = nameof(this.Count);
        _column = expression;
        return this;
    }

    public override bool Equals(object obj)
    {
        return obj is SubQuery<T> query &&
               EqualityComparer<Expression>.Default.Equals(_where, query._where) &&
               EqualityComparer<Expression>.Default.Equals(_column, query._column) &&
               _method == query._method;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(_where, _column, _method);
    }

    public static bool operator <(object t1, SubQuery<T> t2)
    {
        return false;
    }
    public static bool operator ==(object t1, SubQuery<T> t2)
    {
        return false;
    }
    public static bool operator !=(object t1, SubQuery<T> t2)
    {
        return false;
    }
    public static bool operator <=(object t1, SubQuery<T> t2)
    {
        return false;
    }
    public static bool operator >=(object t1, SubQuery<T> t2)
    {
        return false;
    }
    public static bool operator >(object t1, SubQuery<T> t2)
    {
        return false;
    }

    public static explicit operator string(SubQuery<T> v)=> string.Empty;
    
}

step2: use

//in where
var subquery1 = new SubQuery<Student>()
    .Where(a => a.Id <= 15)
    .Select(s => s.Age);

var student1 = context.From<Student>()
    .Where(a=>a.Age>=Operator.Any(subquery1))
    .Select();

//in columns
var subquery2 = new SubQuery<School>()
   .Where<Student,School>((a,b) => a.SchoolId==b.Id)
   .Select(s => s.Name);

var student2 = context.From<Student>()
    .Select(s=>new
    {
        s.Id,
        StudentName = s.Name,
        SchoolName = (string)subquery2//just for build
    });
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy