前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >DataSet的灵活,实体类的方便,DTO的效率:SOD框架的数据容器,打造最适合DDD的ORM框架

DataSet的灵活,实体类的方便,DTO的效率:SOD框架的数据容器,打造最适合DDD的ORM框架

作者头像
用户1177503
发布于 2018-02-27 03:47:08
发布于 2018-02-27 03:47:08
2.7K00
代码可运行
举报
文章被收录于专栏:程序员的SOD蜜程序员的SOD蜜
运行总次数:0
代码可运行

引言:DDD的困惑

最近,我看到园子里面有位朋友的一篇博客 《领域驱动设计系列(一):为何要领域驱动设计? 》文章中有下面一段话,对DDD使用产生的疑问:

•没有正确的使用ORM, 导致数据加载过多,导致系统性能很差。 •为了解决性能问题,就不加载一些导航属性,但是却把DB Entity返回上层,这样对象的一些属性为空,上层使用这个数据时根本不知道什么时间这个属性是有值的,这个是很丑陋的是不是?

博主说的第一个问题,是因为使用ORM的人把实体类的全部属性的数据查询出来了,相当于执行了 select * from table 这样的查询,而实际上,Domain层是不需要这么多额外的数据的。

重新定义一个Domain需要的 DTO? 但这又会导致DTO膨胀,DTO对象满天飞!

所以为了简便,就直接查询出全部属性对应的数据,或者也用EF的Select子句,投影下,但将结果又投影给了另外一个DTO对象或者Entity 对象,这样就使得对象中部分属性为空了,于是又产生了博主的第二个问题。

第二个问题有多严重?

假设某个表有50个字段,这样大的表在很多复杂的系统中是很常见的,于是MAP出来的Entity或者DTO,也有50个属性,而我这次仅需要使用其中的2个属性的值,于是,这个对象上的 48个属性数据都浪费了。

如果这样的DTO对象用在List上且用于分布式环境,那么,这样浪费的网络IO和序列化,凡序列化浪费的CPU,还是比较严重的。

1,准备工作

比如有下面一个用户信息类接口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public interface IUser
    {
        int Age { get; set; }
        string FirstName { get; set; }
        string LasttName { get; set; }
        int UserID { get; set; }
    }

然后根据这个接口,写一个PDF.NET SOD 实体类 UserEntity ,用于持久化数据到数据库或者其它用途:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public class UserEntity:EntityBase, IUser
    {
        public UserEntity()
        {
            TableName = "Users";
            IdentityName = "User ID";
            PrimaryKeys.Add("User ID");
        }

        public int UserID
        {
            get { return getProperty<int>("User ID"); }
            set { setProperty("User ID", value); }
        }

        public string FirstName
        {
            get { return getProperty<string>("First Name"); }
            set { setProperty("First Name", value,20); }
        }

        public string LasttName
        {
            get { return getProperty<string>("Last Name"); }
            set { setProperty("Last Name", value,10); }
        }

        public int Age
        {
            get { return getProperty<int>("Age"); }
            set { setProperty("Age", value); }
        }
    }

还有一个用户类的DTO类 UserDto,可用于分布式系统的数据传输或者解决方案多个项目分层之间的数据传输:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public class UserDto:IUser
    {
        public int Age
        {
            get;
            set;
        }

        public string FirstName
        {
            get;
            set;
        }

        public string LasttName
        {
            get;
            set;
        }

        public int UserID
        {
            get;
            set;
        }
    }

2,SOD框架的实体类

2.1,索引器访问与字段映射

如果 UserEntity user=new UserEntity();此时user 对象里面并没有 UserID 的数据,除非调用了属性的Set方法,此时,可以用下面的代码来验证:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
UserEntity user=new UserEntity();

bool flag=(user["User ID"] ==null);//true

 注意 user["User ID"]   这个地方,SOD的实体类可以当作“索引器”来使用,索引器的Key是实体类属性Map的数据库字段名称,请看UserEntity. UserID 属性的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public int UserID
        {
            get { return getProperty<int>("User ID"); }
            set { setProperty("User ID", value); }
        }

可见我们可以将一个不同的字段名影射到一个属性名上。所以,根据这个定义,访问索引器  user["User ID"]     就等于访问 user实体类的属性 UserID 。 

2.2,“空”的两种境界(null / DBNull.Value) 

从这里我们可以得出结论:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
结论一:
SOD 实体类的属性值默认均为空 (null)

2.2.1,程序中的 null

此时的空,代表数据没有作任何初始化,这种“空”来自以程序中。我们还可以通过查询来进一步验证这种情况的空值:

假如我们的ORM查询语言OQL查询并没有指定要查询实体类的Age属性,那么结果user对象仅有2个数据,并没有3个数据:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
            OQL q3 = OQL.From(uq)
              .Select(uq.UserID, uq.FirstName) //未查询 user.Age 字段
              .Where(uq.FirstName)
           .END;
            UserEntity user3 = context.UserQuery.GetObject(q3);
            //未查询 user.Age 字段,此时查询该字段的值应该是 null
            bool flag3 = (user3["Age"] == null);//true 
            Console.WriteLine("user[\"Age\"] == null :{0}", flag);
            Console.WriteLine("user.Age:{0}", user3.Age);

程序输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
user["Age"] == null :True
user.Age:0

2.2.2,数据库中的 NULL

为了验证SOD 实体类从数据库查询出来的字段的空值是什么情况,我们先插入几条测试数据:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
LocalDbContext context = new LocalDbContext();//自动创建表
           
 //插入几条测试数据
context.Add<UserEntity>(new UserEntity() {  FirstName ="zhang", LasttName="san" });
context.Add<IUser>(new UserDto() { FirstName = "li", LasttName = "si", Age = 21 });
context.Add<IUser>(new UserEntity() { FirstName = "wang", LasttName = "wu", Age = 22 });

我们插入的第一条数据并没有年龄Age 的数据,下面再来查询这条数据,看数据库的值是否为NULL:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
            //查找姓张的一个用户
            UserEntity uq = new UserEntity() { FirstName = "zhang" };
            OQL q = OQL.From(uq)
               .Select(uq.UserID, uq.FirstName, uq.Age)
               .Where(uq.FirstName)
            .END;

            //下面的语句等效
            //UserEntity user2 = EntityQuery<UserEntity>.QueryObject(q,context.CurrentDataBase);
            UserEntity user2 = context.UserQuery.GetObject(q);
            //zhang san 的Age 未插入值,此时查询该字段的值应该是 NULL
            bool flag2 = (user2["Age"] == DBNull.Value);//true 
            Console.WriteLine("user[\"Age\"] == DBNULL.Value :{0}", flag);

注意,这里我们在OQL的Select 子句中,指定了要查询实体类的 Age 属性,如果数据库没有该属性字段的值,它一定是NULL,也就是 程序中说的 NBNULL.Value,看输出结果验证:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
user["Age"] == DBNULL.Value :True
user.Age:0

当然,这里数据库为空,要求表字段是支持可空的。

从这里我们可以得出结论:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
结论二: SODOQL 查询的实体类属性,如果数据库对应的字段值为空,那么实体类内部该属性值也为空(DBNull.Value)

2.2.3 在OQL查询中的NULL

在OQLCompare对象上,可以直接调用 IsNull 方法来判断实体类某个属性在数据库对应的值是否为空,例如下面的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  //查询没有填写 LastName的用户,即LastName==DBNull.Value;
  UserEntity uq = new UserEntity() ;
  OQL q = OQL.From(uq)
        .Select(uq.UserID, uq.FirstName, uq.Age)
        .Where(cmp => cmp.IsNull( uq.LastName))
  .END;

将输出下面的SQL:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Select [UserID],[FistName],[Age] From [User] 
  Where [LastName] IS NULL

2.3,可空类型的问题

在EF等ORM中,要定义一个字段可空,需要定义成可空类型,比如我们的User类,假设定义成EF的实体类,应该是这样子的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public class EFUserEntity
    {
        int? Age { get; set; }
        [MaxLength(20)]
        string? FirstName { get; set; }
        [MaxLength(10)]
        string? LasttName { get; set; }
        [Key]
        [Required]
        int UserID { get; set; } //主键,不可为空
    }

这种可空类型的实体类定义,能够让数据库字段标记为NULL,但是,这个实体类在于DTO类进行转换的时候,总会遇到一些麻烦,因为实体类属性为空,而DTO属性不为空。

有人说,我们把DTO属性也定义为可空类型,不就好了么?

我在想,.NET推出值类型上的可空类型,本意是为了兼容从数据库来的空值,这样,对于 int a; 这个变量来说,可以知道它的值到底是0,还是变量根本没有值,这是未知的,而int? a; 这个变量完美的解决了这个问题。

但是,如果你的服务的客户端不是.net,而是JAVA,JS,或者其它不支持可空类型的语言,这种有可空类型属性的DTO就遇上麻烦了

所以,SOD的实体类,属性可以定义为非可空类型的,但是属性的内部值,null或者 DBNull.Value 都是可以的。

3,数据的容器 

SOD实体类可以仅看作一个数据容器,又可以看作一个ORM的实体类,大大增加了使用的灵活性和查询的效率。

对于上面的查询,不管Age属性在实体类里面是 

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bool flag=(user2["Age"]==NBNull.Value);//true

还是 

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bool flag=(user3["Age"]==null);//true

当外面获取Age属性的时候,都是Age的默认值0:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 int age=user2.Age;//0
 int age=user3.Age;//0

 这些数据在实体类中是怎么存储的呢?原来,实体类内部有一个类似于“名-值对”的2个数组,用于存储实体类映射的数据库字段名和字段的值,这个结构就是SOD框架的中的  PropertyNameValues 类,定义很简单:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public class PropertyNameValues
    {
        public string[] PropertyNames { get; set; }
        public object[] PropertyValues { get; set; }
    }

所以实体类的字段值是存储在Object对象上,这也是 为何SOD实体类可以处理2种空值null,DBNull.Value的原因。当然你也可以存其它内容,只要属性类型兼容即可。比如属性类型是long,而数据库字段的值类型是 int ,这在SOD实体类是允许的。

3.1,综合示例

下面这个查询,动态查询一个实体类的属性是否等于指定的值,或者该属性对应的字段在数据库是否为空,而实现动态查询的关键,是使用索引器, 如下面的BatchNumber 属性,查询此属性值是否为0或者是否为空:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private OQL FilterQuery(EntityBase entity)
{
            if (entity is IExportTable)
            {
                entity["BatchNumber"] = 0;
                OQL q = OQL.From(entity)
                    .Select()
                    .Where(cmp => cmp.EqualValue(entity["BatchNumber"]) | cmp.IsNull(entity["BatchNumber"]))
                    .END;
                return q;

            }
            return null;
}

另外,这个值的可变性,使得SOD框架处理 枚举属性 非常方便,因为,Enum 与int 类型是兼容的,可以相互转换,参看这篇文章:

实体类的枚举属性--原来支持枚举类型这么简单,没有EF5.0也可以

属性值的可变性,除了上面的好处,还有什么好处?

好处大大的,这意味着 PropertyNames,PropertyValues 的长度是可变的,就像前面的例子,查询了Age属性,实体类的值有3个,而不查询,那么值只有2个。

假设实体类有50个属性,本次只查询了2个属性,那么SOD的实体类实际传输的数据就只有2个,而不是50个,这将大大节省数据传输量。

这个可以通过SOD实体类的序列化结果来验证。

4,在分布式系统上使用实体类

4.1,实体类的序列化与反序列化

这里必然绕不开实体类的序列化与反序列化,现在最新的SOD框架已经内置支持,参考下面的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 //查找姓张的一个用户
            UserEntity uq = new UserEntity() { FirstName = "zhang" };

            OQL q3 = OQL.From(uq)
              .Select(uq.UserID, uq.FirstName) //未查询 user.Age 字段
              .Where(uq.FirstName)
           .END;
            UserEntity user3 = context.UserQuery.GetObject(q3);

            Console.WriteLine("实体类序列化测试");
            var entityNameValues= user3.GetNameValues();
            PropertyNameValuesSerializer ser = new PropertyNameValuesSerializer(entityNameValues);
            string strEntity = ser.Serializer();
            Console.WriteLine(strEntity);
            Console.WriteLine("成功");
            //
            Console.WriteLine("反序列化测试");
            PropertyNameValuesSerializer des = new PropertyNameValuesSerializer(null);
            UserEntity desUser = des.Deserialize<UserEntity>(strEntity);
            Console.WriteLine("成功");

下面是序列化结果的输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?xml version="1.0" encoding="utf-16"?>
<PropertyNameValues xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <PropertyNames>
    <string>User ID</string>
    <string>First Name</string>
  </PropertyNames>
  <PropertyValues>
    <anyType xsi:type="xsd:int">26</anyType>
    <anyType xsi:type="xsd:string">zhang</anyType>
  </PropertyValues>
</PropertyNameValues>

可见,以这种方式序列化传输的数据量,将是很少的。当然,你还可以更改成JSOn序列化,这样数据更少,缺点是数据元数据没有了。

4.2,Entity,DomainModel,DTO 之间的数据拷贝

三层或者多层架构,或者DDD架构,少不了Entity,DomainModel,DTO 之间的数据拷贝,如果数据结构高度相似,可以使用AutoMapper之类的工具,而在SOD框架内,使用了速度最快的属性拷贝方案,参见之前我写的博客文章:

 《使用反射+缓存+委托,实现一个不同对象之间同名同类型属性值的快速拷贝

另外,如果是从实体类到DTO,或者DTO到实体类的数据复制,在EntityBase上提供了 MapFromMapTo方法,例如下面使用的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
IUser TestMapFromDTO(IUser data)
{
 IUser user = EntityBuilder.CreateEntity<IUser>();
 ((entityBase)user).MapFrom(data);
 return user;
}

当然,还有CopyTo方法,只要你引用了框架扩展 PWMIS.Core.Extension.dll

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
using PWMIS.Core.Extensions;
... ...

//CoyTo 创建一个实例对象
ImplCarInfo icResult= info.CopyTo<ImplCarInfo>(null);

//CopyTo 填充一个实例对象
ImplCarInfo icResult2 = new ImplCarInfo();
info.CopyTo<ImplCarInfo>(icResult2);

 将实体类的数据拷贝到DTO对象的时候,推荐下面这种直接调用 这种方式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
DTOXXX dto=EntityObject.CopyTo<DTOXX>()

 4.3 在WCF,WebService 上使用"实体类"

有很多朋友想在WebService上直接使用SOD实体类,但是由于实体类继承自实体类接口,默认的XML序列化会失败,不过WCF采用了不同的序列化方式,可以序列化SOD的实体类,但是会将实体类内部的一些数据也序列化过去,增大数据传输量,因此,我一般都是建议在WCF,WebService 的服务方法上使用DTO对象,而不是SOD实体类。可以通过上面的方法实现实体类与DTO之间的转换。

但是,采用DTO对象会导致“数据更新冗余”,比如某个属性没有修改,DTO上也会有对应的默认值的,比如 userEntity.Age 属性,如果从未赋值,那么 userDto.Age 也会有默认值 0 ,而传输这个默认值0 并没有意义,并且有可能让服务后段的ORM代码将这个 0 更新到数据库中,这就是数据更新容易。

有时候,我们希望只更新已经改变的数据,没有改变的数据不更新,那么此时WCF等服务端的方法,采用DTO对象就无法做到了。幸好,SOD的实体类提供了仅仅获取更改过的数据的方法,请看下面的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 //序列化之后的属性是否修改的情况测试,下面的实体类,LastName 属性没有被修改
 UserEntity user4 = new UserEntity() {  UserID =100, Age=20, FirstName ="zhang san"};
 entityNameValues = user4.GetChangedValues();
 PropertyNameValuesSerializer ser = new PropertyNameValuesSerializer(entityNameValues);
 string strEntity = ser.Serializer();
 Console.WriteLine(strEntity);
 Console.WriteLine("成功");
 //
 Console.WriteLine("反序列化测试");
 PropertyNameValuesSerializer des = new PropertyNameValuesSerializer(null);
 UserEntity desUser = des.Deserialize<UserEntity>(strEntity);
 Console.WriteLine("成功");

这里需要调用实体类的 GetChangedValues 方法,这样序列化的时候就只序列化了修改过的数据了,并且反序列化之后,数据也还原了之前的“修改状态”,拿这样的实体类去更新数据库,就不会出现“数据更新冗余”了。

下面是一个WCF方法示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void Dosomething(PropertyNameValues para)
{
  UserEntity user = new UserEntity();
  PropertyNameValuesSerializer ser = new PropertyNameValuesSerializer(para);
  ser.FillEntity(user);
  //To Dosomething.....
}

注意:该功能需要SOD框架的 5.2.3.0527 版本以上支持

5,SOD框架 的CodeFirst支持

 最新版的SOD框架(PDF.NET SOD)已经可以方便的支持CodeFirst开发了,使用很简单,调用只需要一行代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 Console.WriteLine("第一次运行,将检查并创建数据表");
 LocalDbContext context = new LocalDbContext();//自动创建表

而这个LocalDbContext 的定义也不复杂:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class LocalDbContext : DbContext // 内部会根据 local 连接字符串名字,决定是否使用 SqlServerDbContext

        public LocalDbContext()
            : base("local") 
        {
            //local 是连接字符串名字
        }

        #region 父类抽象方法的实现

        protected override bool CheckAllTableExists()
        {
            //创建用户表
            CheckTableExists<UserEntity>();
            return true;
        }

        #endregion
}

综合结论:

 所以SOD实体类对用户而言是透明的,它并没有增加使用的复杂性,又可以很好的控制数据量,还可以让你知道数据来自哪里,简单而又强大。

这样的ORM,才是合适DDD的ORM,当然,SOD不仅仅是一个ORM,它还有SQL-MAP和DataControl,具体可以看框架官网 http://www.pwmis.com/sqlmap ,9年历史铸就的成果,坚固可靠。

附注:

下面是本文说明中使用的完整代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("====**************** PDF.NET SOD 控制台测试程序 **************====");
            Assembly coreAss = Assembly.GetAssembly(typeof(AdoHelper));//获得引用程序集
            Console.WriteLine("框架核心程序集 PWMIS.Core Version:{0}", coreAss.GetName().Version.ToString());
            Console.WriteLine();
            Console.WriteLine("  应用程序配置文件默认的数据库配置信息:\r\n  当前使用的数据库类型是:{0}\r\n  连接字符串为:{1}\r\n  请确保数据库服务器和数据库是否有效,\r\n继续请回车,退出请输入字母 Q ."
                , MyDB.Instance.CurrentDBMSType.ToString(), MyDB.Instance.ConnectionString);
            Console.WriteLine("=====Power by Bluedoctor,2015.2.10 http://www.pwmis.com/sqlmap ====");
            string read = Console.ReadLine();
            if (read.ToUpper() == "Q")
                return;

            Console.WriteLine();
            Console.WriteLine("-------PDF.NET SOD 实体类 测试---------");
            //注册实体类
            EntityBuilder.RegisterType(typeof(IUser), typeof(UserEntity));

            UserEntity user = EntityBuilder.CreateEntity<IUser>() as UserEntity;
            bool flag = (user["User ID"] == null);//true
            Console.WriteLine("user[\"User ID\"] == null :{0}",flag);
            Console.WriteLine("user.UserID:{0}", user.UserID);

            Console.WriteLine("第一次运行,将检查并创建数据表");
            LocalDbContext context = new LocalDbContext();//自动创建表
            //删除测试数据
            OQL deleteQ = OQL.From(user)
                .Delete()
                .Where(cmp=>cmp.Comparer(user.UserID,">",0)) //为了安全,不带Where条件是不会全部删除数据的
                .END;
            context.UserQuery.ExecuteOql(deleteQ);
            Console.WriteLine("插入3条测试数据");
            //插入几条测试数据
            context.Add<UserEntity>(new UserEntity() {  FirstName ="zhang", LasttName="san" });
            context.Add<IUser>(new UserDto() { FirstName = "li", LasttName = "si", Age = 21 });
            context.Add<IUser>(new UserEntity() { FirstName = "wang", LasttName = "wu", Age = 22 });

            //查找姓张的一个用户
            UserEntity uq = new UserEntity() { FirstName = "zhang" };
            OQL q = OQL.From(uq)
               .Select(uq.UserID, uq.FirstName, uq.Age)
               .Where(uq.FirstName)
            .END;

            //下面的语句等效
            //UserEntity user2 = EntityQuery<UserEntity>.QueryObject(q,context.CurrentDataBase);
            UserEntity user2 = context.UserQuery.GetObject(q);
            //zhang san 的Age 未插入值,此时查询该字段的值应该是 NULL
            bool flag2 = (user2["Age"] == DBNull.Value);//true 
            Console.WriteLine("user[\"Age\"] == DBNULL.Value :{0}", flag);
            Console.WriteLine("user.Age:{0}", user2.Age);

            OQL q3 = OQL.From(uq)
              .Select(uq.UserID, uq.FirstName) //未查询 user.Age 字段
              .Where(uq.FirstName)
           .END;
            UserEntity user3 = context.UserQuery.GetObject(q3);
            //未查询 user.Age 字段,此时查询该字段的值应该是 null
            bool flag3 = (user3["Age"] == null);//true 
            Console.WriteLine("user[\"Age\"] == null :{0}", flag);
            Console.WriteLine("user.Age:{0}", user3.Age);

            Console.WriteLine("实体类序列化测试");
            var entityNameValues= user3.GetNameValues();
            PropertyNameValuesSerializer ser = new PropertyNameValuesSerializer(entityNameValues);
            string strEntity = ser.Serializer();
            Console.WriteLine(strEntity);
            Console.WriteLine("成功");
            //
            Console.WriteLine("反序列化测试");
            PropertyNameValuesSerializer des = new PropertyNameValuesSerializer(null);
            UserEntity desUser = des.Deserialize<UserEntity>(strEntity);
            Console.WriteLine("成功");

            Console.WriteLine();
            Console.WriteLine("----测试完毕,回车结束-----");
            Console.ReadLine();
        }
    }

图片的效果要好些:

有关该测试程序的完整下载和查看,请看框架开源项目地址:

http://pwmis.codeplex.com/SourceControl/latest#SOD/Test/EntityTest-2013/Program.cs

其它:

一年之计在于春,2015开篇:PDF.NET SOD Ver 5.1完全开源

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
不用插件直接同步wordpress文章日志到新浪微博
社会化媒体营销可以为网站带来流量,如果你的社会化媒体账号的粉丝技术够大的话!社会化媒体营销国内做得比较好的算新浪微博了。那么我们要怎样同步wordpress文章日志到新浪微博呢?当然你可以使用插件来实现,但我们力求精简,插件能少用尽量少用,wordpress运行效率才会高,下面我们介绍一下不用插件直接同步wordpress文章日志到新浪微博
ytkah
2022/03/14
4840
同步 WordPress 博客日志到新浪微博
该功能已经整合到新浪连接插件中,请直接下载新浪连接插件来使用同步 WordPress 博客日志到新浪微博功能。
Denis
2023/04/15
3300
WordPress发布文章自动同步到新浪微博(带特色图片)
WordPress 发博客后自动同步到新浪微博,这是我从无主题博客看到的方法,一直沿用至今。感觉对博客宣传和提升“逼格”都有显著的作用: 一、老版代码 先来看一下无主题博客分享的代码: functio
张戈
2018/03/23
1.7K0
WordPress发布文章自动同步到新浪微博(带特色图片)
WordPress发布文章自动同步到新浪微博(带特色图片)
整体来源于张戈博客,本处仅是修改添加了一处显示文章分类的小功能,若是一篇文章有多个分类,默认使用第一个。经测试好像对于七牛中设置了空间防盗链的不太友好,可能会报2007错误。
WindCoder
2018/09/19
2.8K1
WordPress发布文章自动同步到新浪微博(带特色图片)
同步 WordPress 博客日志到 Follow5
Follow5 是一个以"我"为中心的分享与联络的平台,Follow5 的目标:让分享更简单、更随意、更轻松。作为一个标准的微博客,显然 Follow5 也支持 API,下面我讲解下如果使用 Follow5 的 API 同步 WordPress 博客日志到 Follow5:
Denis
2023/04/16
2170
Wordpress自动推送新文章收录百度
百度站长工具提供了很多种页面收录方式,在百度资源搜索平台提供了多种提交方式,比如自动提交,手动提交,以及sitemap。
宋天伦
2020/07/16
4100
自定义wordpress侧边栏小工具
作者:matrix 被围观: 1,726 次 发布时间:2014-01-25 分类:Wordpress | 4 条评论 »
HHTjim 部落格
2022/09/26
3110
自定义wordpress侧边栏小工具
WordPress发布文章主动推送到百度,加快收录保护原创
工作实在太忙,也没时间打理网站。最近公司额外交待了一些网站 SEO 方面的优化任务让我关注(这就是啥都要会、啥都要做的苦逼运维的真实写照了...)。 于是抽空看了下百度站长平台,至少看到了 2 个新消
张戈
2018/03/23
1.6K0
WordPress发布文章主动推送到百度,加快收录保护原创
WordPress IndexNow提交教程,让搜索引擎快速收录
IndexNow 是一种协议,允许网站所有者将其网站上的新内容或修改内容通知多个搜索引擎。目前有 Bing 和 Yandex 加入,据说 Google 也正在尝试接入 IndexNow。你只需要在网站页面发生变更时去通过这个协议 Ping 一下搜索引擎,这样加入 IndexNow 的搜索引擎就会被成功通知到,有利于搜索引擎快速的发现新 URL,实现网站的快速收录。
张子凡
2022/11/02
1.2K0
WordPress IndexNow提交教程,让搜索引擎快速收录
WordPress免插件仅代码实现文章浏览次数的方法(3)
在WordPress中为每一篇文章提供个“浏览次数”计数,一来可以间接地给访客一种文章有价值的暗示,二来方便自己获取相关数据(访客的内容偏好等等),三貌似想不到了。本站DeveWork.com之前提供
Jeff
2018/01/19
8410
只需两步就能实现WordPress自动检查文章是否被百度收录
前言 本次教程是实现WordPress自动检查文章是否被百度收录,如果收录了就显示“已经收录”,如果没有收录就显示为“百度未收录”,会直接在百度站长平台提交当前页面的URL了,方便蜘蛛发现新文章,从而加快文章的收录速度。 使用教程 1、进入Wordpress后台,点击外观,然后点编辑,在右边选择“模板函数”functions.php文件,添加如下代码并保存: function baidu_check($url){ global $wpdb; $post_id = ( null === $p
吾爱乐享
2018/07/13
4710
WordPress免插件仅代码实现文章浏览次数的方法(2)
上一篇文章中已经给出了一种纯代码实现实现文章浏览次数的方法,今天再来提供另外一种。如果之前的不能实现,可以用这个来试试。代码来源于willian大师的my_visitor插件,由DH博客进行精简化,下
Jeff
2018/01/19
8010
WordPress发布文章同步到新浪微博失败的问题解决与分享
张戈博客很久之前分享过一篇 WordPress 发布文章同步到新浪微博 的文章,但经常有站长留言反馈同步失败,我一直觉得是代码部署问题。 最近很长一段时间,张戈博客也无法同步,我又觉得是微博自身的问题
张戈
2018/03/21
1.1K0
WordPress发布文章同步到新浪微博失败的问题解决与分享
WordPress秒变谷歌AMP加速移动页面并自动推送
谷歌 AMP(Accelerated Mobile Pages)字面意思就是“加速移动网页”,官方解释是:Accelerated Mobile Page (AMP) 是根据开放源代码规范设计的网页。经过验证的 AMP 网页会缓存在 Google 的 AMP 缓存中,从而可以更快速地呈现给用户。 AMP 页面是大大简化了移动页面,旨在提高针对移动页面的访问速度,AMP 的特点如下: AMP 的 HTML 代码是标准 HTML 的一个子集,大大简化了 html 的代码,部分 Html 代码将不再适用,如 tab
沈唁
2018/05/24
2K0
wordpress 内容备份镜像站点建立方法及注意事项
作为虾米级站长一枚,实则是不懂代码的菜鸟,由于自己的站点是小水管主机,而且稳定性也难以保障,在很多访客的建议下,也想建立一个内容镜像站点,以实现当主站的主机维护时,能够有一个备用站点让访客访问。
墨铺网
2019/07/30
9910
WordPress 纯代码自动发布文章推送百度加快收录
以前我们发布文章就自动推送到百度熊掌号中,不过近期百度已经将移动专区的天级收录功能下线了,改为快速收录功能,连 API 也改了,所以我们以前自动推送给熊掌号的代码已经无效,需要调整其中的 API 接口等内容。今天跟大家分享 WordPress 站点如何用代码实现发布文章即主动推送到百度快速收录中。
你的明明呐丶
2022/06/27
6860
WordPress 纯代码自动发布文章推送百度加快收录
让博客支持使用 ChatGPT 生成文章摘要是一种什么样的体验?
本文讲述了博主为了给自己的 Argon 主题添加基于 ChatGPT 的人工智能摘要功能而付出的努力。文章介绍了开发流程,包括使用 haozi-team/chatgpt-php 库对接 OpenAI 接口,修改 settings.php 和 inc/fun/post-extra-meta-editor.php 文件添加全局和文章单独的设置,以允许用户配置 OpenAI 地址,密钥,以及使用 AI 摘要功能的选项,并在 inc/fun/post.php 和 template-parts/entry/excerpt.php 中添加页面样式。最终,博主满意地得出结论:这个功能非常好用,易于操作,能够很好地满足需求。
HikariLan贺兰星辰
2023/04/28
3320
让博客支持使用 ChatGPT 生成文章摘要是一种什么样的体验?
使用这三种方法提交 WordPress 博客链接到百度站长,百度收录立刻翻倍
建好 WordPress 站点之后,最期待的事情就是搜索引擎收录自己的站点,如何加速这一过程呢?对于国内用户来说,就是提交链接到百度。
Denis
2023/04/13
1.1K0
使用这三种方法提交 WordPress 博客链接到百度站长,百度收录立刻翻倍
WordPress给文章添加百度是否已收录查询和显示功能(自定义栏目优化版)
文章页面显示百度是否收录这个功能在张戈博客已经测试有一段时间了。最开始的代码也是从网络上找的,只是自己用,所以也就没想着分享了,毕竟是人家的成果,而且自行百度也是可以找到的! 不过,既然有朋友问到可否分享下添加这个功能的教程,所以决定整理分享一下。 我一直是一个中度强迫症的完美主义者,所以别人已经分享的东西我一般不喜欢分享第二遍!但昨晚熬夜到三点,除了在新浪 SAE 搭建了一个二维码 API 之外,还将百度收录查询这个功能实现了自定义栏目优化! 功能名称:给 WordPress 添加百度是否收录的查询与显示
张戈
2018/03/23
1.7K0
WordPress缓存类WP_Object_Cache
作者:matrix 被围观: 5,061 次 发布时间:2015-07-31 分类:Wordpress 零零星星 | 10 条评论 »
HHTjim 部落格
2022/09/26
2190
推荐阅读
相关推荐
不用插件直接同步wordpress文章日志到新浪微博
更多 >
LV.0
这个人很懒,什么都没有留下~
目录
  • 引言:DDD的困惑
  • 1,准备工作
  • 2,SOD框架的实体类
    • 2.1,索引器访问与字段映射
    • 2.2,“空”的两种境界(null / DBNull.Value) 
      • 2.2.1,程序中的 null
      • 2.2.2,数据库中的 NULL
      • 2.2.3 在OQL查询中的NULL
    • 2.3,可空类型的问题
  • 3,数据的容器 
    • 3.1,综合示例
  • 4,在分布式系统上使用实体类
    • 4.1,实体类的序列化与反序列化
    • 4.2,Entity,DomainModel,DTO 之间的数据拷贝
    •  4.3 在WCF,WebService 上使用"实体类"
  • 5,SOD框架 的CodeFirst支持
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档