YGFramework游戏框架文档

news/2024/11/13 11:09:22/文章来源:https://www.cnblogs.com/xiaogen/p/18538277

YGFramework游戏框架文档

我为什么要写这套框架

大一大二时候参加了多个未来制作人大赛和48小时极限开发比赛,发现我们写的项目耦合性高,层次不够清晰,随时相互调用,维护困难。以至于在进行版本控制,上传到Plastic SCM时,导致节点冲突而部分功能被冲突掉,最后只能返工重做。

我在暑假也了解多许多架构,发现了两个问题,一是很多框架不开源,这会直接造成部分错误无法修复或者修复困难。二是很多框架组件和中间件过于繁多,学习成本加大,比如我写音频组件更本不会用到空间音频,而很多框架直接用封装好的空间音频组件当成音频组件使用,这也会造成优化的问题。

因此我决定自己实现一套框架,这套框架最大的特点是简单易上手,并且能让项目的代码更加清晰。

概述

主要由组成:

  • Architecture (架构)
  • System (系统)
  • Model (模型)
  • Utility (实用工具)
  • Command (命令)
  • Query (查询)
  • TypeEventSystem (事件系统)
  • IOC (依赖注入容器)
  • BindableProperty (绑定属性)

架构层级:

表现层:IController 接口,负责接收输入和当状态变化时更新表现,一般情况下 MonoBehaviour 均为表现层对象。

系统层:ISystem 接口,帮助 IController 承担一部分逻辑,在多个表现层共享的逻辑,比如计时系统、商城系统、成就系统等。

模型层:IModel 接口,负责数据的定义以及数据的增删改查方法的的提供。

工具层:IUtility 接口,负责提供基础设施,比如存储方法、序列化方法、网络链接方法、蓝牙方法、SDK、框架集成等。

使用规则:

• IController 更改 ISystem、IModel 的状态必须用 Command。

• ISystem、IModel 状态发生变更后通知 IController 必须用事件 或 BindableProeprty。

• IController 可以获取 ISystem、IModel 对象来进行数据查询。

• ICommand 不能有状态。

• 上层可以直接获取下层对象,下层不能获取上层对象。

• 下层像上层通信用事件。

• 上层向下层通信用方法调用,IController 的交互逻辑为特使情况,只能用 Command。

插入设计图片

YGFramework具体设计思路

YGFramework 的设计理念是通过依赖注入(IOC)和事件驱动的方式,实现模块间的松耦合和高内聚,从而提高代码的可维护性和扩展性。主要包括以下几个方面:

  • 模块解耦:通过接口和依赖注入,使得系统、模型和实用工具彼此独立,方便后期扩展或修改。
  • 事件驱动:通过事件系统,模块间可以通过发布和订阅事件的方式进行通信,避免直接的依赖关系。
  • 命令与查询分离:使用命令模式执行操作,使用查询模式获取数据,遵循 CQRS(Command Query Responsibility Segregation)原则。
  • 依赖倒置原则(Dependence Inversion Principle):程序要依赖于抽象接口,不要依赖于具体实现。

架构(Architecture)

架构是框架的核心,负责系统、模型、实用工具的注册和管理,同时提供发送命令、查询和事件的功能。

IArchitecture 接口

IArchitecture 定义了架构的基本操作,包括:

  • 注册方法:注册系统(RegisterSystem)、模型(RegisterModel)和实用工具(RegisterUtility)。
  • 获取方法:获取系统(GetSystem)、模型(GetModel)和实用工具(GetUtility)。
  • 发送方法:发送命令(SendCommand)、查询(SendQuery)和事件(SendEvent)。
  • 事件注册:注册和注销事件监听器(RegisterEvent 和 UnRegisterEvent)。
    public interface IArchitecture{#region 注册(Register)/// <summary>/// 注册系统/// </summary>/// <typeparam name="T"></typeparam>/// <param name="system"></param>void RegisterSystem<T>(T system) where T : ISystem;/// <summary>/// 注册Model/// </summary>/// <typeparam name="TModel"></typeparam>/// <param name="model"></param>void RegisterModel<TModel>(TModel model) where TModel : IModel;/// <summary>/// 注册工具/// </summary>/// <typeparam name="T"></typeparam>/// <param name="utility"></param>void RegisterUtility<T>(T utility)where T:IUtility;#endregion#region 获取(Get)/// <summary>/// 获取工具/// </summary>/// <typeparam name="T"></typeparam>/// <returns></returns>T GetUtility<T>() where T : class,IUtility;/// <summary>/// 获取Model/// </summary>/// <typeparam name="T"></typeparam>/// <returns></returns>T GetModel <T>() where T : class,IModel;/// <summary>/// 获取system/// </summary>/// <typeparam name="T"></typeparam>/// <returns></returns>T GetSystem<T>() where T : class,ISystem;#endregion#region 发送命令(SendCommand)/// <summary>/// 发送命令/// </summary>/// <typeparam name="T"></typeparam>void SendCommand<T>()where T:ICommand,new();/// <summary>/// 发送命令/// </summary>/// <typeparam name="T"></typeparam>/// <param name="command"></param>void SendCommand<T>(ICommand command) where T:ICommand;#endregionvoid SendEvent<T>() where T:new();void SendEvent<T>(T e);IUnRegister RegisterEvent<T>(Action<T> onEvent);void UnRegisterEvent<T>(Action<T> onEvent);}

Architecture 抽象类

Architecture<T> 实现了 IArchitecture 接口,提供了架构的具体实现。它采用了单例模式,确保整个应用程序中只有一个架构实例。类中的 Init() 方法用于初始化架构,确保所有系统和模型都在使用前得到初始化。

泛型限制为无参数构造,并且实现IArchitecture接口

 public abstract class Architecture<T> : IArchitecture where T : Architecture<T>, new()

用MakeSureArchitecture方法,确保Architecture初始化,用List作为缓存初始化各个层次

        private List<IModel> mModels = new List<IModel> ();private List<ISystem> mSystems = new List<ISystem> ();public static Action<T> OnRegisterPatch = architecture => { };private static T mArchitecture;public static IArchitecture Interface{get {if(mArchitecture == null){MakeSureArchitecture();}return mArchitecture; }}static void MakeSureArchitecture(){if (mArchitecture == null){mArchitecture = new T();mArchitecture.Init();OnRegisterPatch?.Invoke(mArchitecture);foreach(var architectureModel in mArchitecture.mModels){architectureModel.Init();//model层比system层更底层,先初始化}mArchitecture.mModels.Clear ();foreach (var architectureSystem in mArchitecture.mSystems){architectureSystem.Init();}mArchitecture.mSystems.Clear();mArchitecture.mInited = true;}}

注意在model层和system层初始化之前有一个静态委托 public static Action OnRegisterPatch = architecture => { };//允许开发者在架构注册过程中注入额外的逻辑,而不需要修改框架的核心代码。例如,你可以在架构的注册阶段执行一些特定的初始化逻辑,或者注册额外的服务、组件等。

还可以用OnRegisterPatch 来添加调试信息或日志记录,以便在架构注册过程中追踪架构的状态和行为。

Architecture实现IArchitecture的各个方法,里面运用到了IOC容器和自定义的事件系统

private IOCContainer mContainer = new();protected abstract void Init();[Obsolete("简版框架已弃用",true)]public static void Register<TInstance>(TInstance instance){MakeSureArchitecture();mArchitecture.mContainer.Register<TInstance>(instance);}public void RegisterModel<TModel>(TModel model) where TModel : IModel{model.SetArchitecture(this);mContainer.Register<TModel>(model);if (!mInited){mModels.Add(model);}else{model.Init();}}public void RegisterUtility<TUtility>(TUtility utility) where TUtility : IUtility{mContainer.Register<TUtility>(utility);}public void RegisterSystem<TSystem>(TSystem system) where TSystem : ISystem{system.SetArchitecture(this);mContainer.Register<TSystem>(system);if (!mInited){mSystems.Add(system);}else{system.Init();}}[Obsolete("简版框架Get已经弃用",true)]public static TInstance Get<TInstance>() where TInstance : class{MakeSureArchitecture();return mArchitecture.mContainer.Get<TInstance>();}public  TUtility GetUtility<TUtility>() where TUtility : class, IUtility{return mContainer.Get<TUtility>();}TModel IArchitecture.GetModel<TModel>(){return mContainer.Get<TModel>();}TSystem IArchitecture.GetSystem<TSystem>(){return mContainer.Get<TSystem>();}public void SendCommand<TCommand>() where TCommand : ICommand, new(){var command = new TCommand();command.SetArchitecture(this);command.Execute();}public void SendCommand<TCommand>(ICommand command) where TCommand : ICommand{command.SetArchitecture(this);command.Execute();}ITypeEventSystem mTypeEventSystem = new TypeEventSystem();public void SendEvent<TEvent>() where TEvent : new(){mTypeEventSystem.Send<TEvent>();}public void SendEvent<TEvent>(TEvent e){mTypeEventSystem.Send<TEvent>(e);}public IUnRegister RegisterEvent<TEvent>(Action<TEvent> onEvent){return mTypeEventSystem.Register<TEvent>(onEvent);}public void UnRegisterEvent<TEvent>(Action<TEvent> onEvent){mTypeEventSystem.UnRegister<TEvent>(onEvent);}}

以上代码会很容易发现我用的是接口实现这种形式注册和获取对象的方式,这种方式是符合依赖倒置原则,其核心思想是高层模块不应该依赖于低层模块,两者都应该依赖于抽象,并且抽象不应该依赖于细节,细节应该依赖于抽象

好处如下:

• 接口设计与实现分成两个步骤,接口设计时可以专注于设计,实现时可以专注于实现。

• 接口设计时专注于设计可以减少系统设计时的干扰。

• 实现是可以替换的,比如一个接口叫 IStorage,其实现可以是 PlayerPrefsStorage、EasySaveStorage、EdtiroPrefsStorage,等切换时候只需要一行代码就可以切换了。

• 当实现细节(比如 PlayerPrefsStorage)发生变化时,其引用接口(比如 IStorage)类里的代码不会跟着改变,降低耦合。

• 等等

好处有很多,当然不好的地方就是可能要多写一点代码,但是对于大规模项目来说这点代码量来换取更健康的项目状态是很划算的。

控制器(Controller)

控制器是系统逻辑的封装,负责与系统、模型交互并协调它们的工作。

IController 接口

IController 继承了一系列扩展接口,这些接口赋予IController能力(与其说是赋予不如说是限制,接口的显示调用+扩展方法进行限制)
YGFramework没有将视图和控制分开,Controller就已经是顶层了,所以能直接获取system和model,当想要底层状态发生改变时发送command。

namespace FrameworkDesign
{public interface IController : IBelongToArchitecture, ICanGetSystem, ICanGetModel, ICanSendCommand,ICanRegisterEvent{}
}//public abstract class Abstract_yourManagerName_Controller : MonoBehaviour, IController//{//    IArchitecture IBelongToArchitecture.GetArchitecture()//    {//        return yourManagerName.Interface;//        //实现接口IController变成继承Abstract_yourManagerName_Controller//        //为了框架通用性这里不写//    }//}

IBelongToArchitecture接口(规则)

    public interface IBelongToArchitecture{IArchitecture GetArchitecture();}

该接口在框架中的作用主要是提供一种结构化的方式,使实现该接口的类能够访问其所属的 IArchitecture 实例。这个接口的设计意图是为了实现依赖倒置原则,方便类在执行过程中获取到它们所依赖的架构服务或模块。

ICanGetSystem接口(规则)

    public interface ICanGetSystem : IBelongToArchitecture{}public static class CanGetSystemExtension{public static T GetSystem<T>(this ICanGetSystem self) where T : class, ISystem{return self.GetArchitecture().GetSystem<T>();}}

系统(System)

系统是架构中用于处理业务逻辑的模块,代表某一领域的操作集合。

ISystem 接口

ISystem位于表现层之下,因此可以获取model,utilitty,和同层的system,框架规定下层通知表现层时用事件,因此也能发送事件

    public interface ISystem : IBelongToArchitecture,ICanSetArchitecture,ICanGetModel,ICanGetUtility,ICanRegisterEvent,ICanSendCommand,ICanGetSystem,ICanSendEvent{void Init();}

ICanSetArchitecture接口(规则)

这里发现多了一个ICanSetArchitecture接口约束实际上之前void SetArchitecture(IArchitecture architecture);是写在IBelongToArchitecture里的,但发现初始化会造成递归调用,使其栈溢出故而分开。

    public interface ICanSetArchitecture{void SetArchitecture(IArchitecture architecture);}

AbstractSystem 抽象类

AbstractSystem 提供了 ISystem 的基本实现,开发者可以通过继承该类来实现具体的系统,并在 OnInit() 方法中定义初始化逻辑。

    public abstract class AbstractSystem : ISystem{private IArchitecture mArchitecture = null;IArchitecture IBelongToArchitecture.GetArchitecture(){return mArchitecture;}void ICanSetArchitecture.SetArchitecture(IArchitecture architecture){mArchitecture = architecture;}void ISystem.Init(){OnInit();}protected abstract void OnInit();}

模型(Model)

模型负责管理数据和状态,并提供对数据的操作接口。

IModel 接口

IModel 定义了模型的基本行为和扩展接口,主要用于获取实用工具和发送事件。

    public interface IModel : IBelongToArchitecture,ICanSetArchitecture,ICanGetUtility,ICanSendEvent{void Init();}

AbstractModel抽象类

AbstractModel是模型的抽象实现,开发者可以通过继承该类来实现具体的模型逻辑,并在 OnInit() 方法中定义初始化逻辑。

同样的这样设计符合依赖倒置原则

    public abstract class AbstractModel : IModel{private IArchitecture mArchitecture = null;IArchitecture IBelongToArchitecture.GetArchitecture(){return mArchitecture;}void ICanSetArchitecture.SetArchitecture(IArchitecture architecture){mArchitecture = architecture;}void IModel.Init(){OnInit();}protected abstract void OnInit();}

实用工具(Utility)

实用工具用于封装常用的、与具体业务无关的功能,例如日志、文件操作等。

IUtility 接口

IUtility 是一个标记接口,表示某个类是一个实用工具,可以在架构中注册和获取。因为与业务无关,所以不用获取任何东西。

    public interface IUtility{}

命令(Command)

命令模式用于封装对系统或模型的操作,允许通过 SendCommand 方法来执行某个操作。

ICommand接口

ICommand定义了命令的行为,包含 Execute() 方法,表示命令的执行逻辑。

    public interface ICommand:IBelongToArchitecture,ICanSetArchitecture,ICanGetSystem,ICanGetModel,ICanGetUtility,ICanSendEvent,ICanSendCommand{void Execute();}

AbstractCommand抽象类

AbstractCommand 提供了命令的基本实现,开发者可以通过继承该类来实现具体的命令,并在 OnExecute() 方法中定义执行逻辑。

    public abstract class AbstractCommand : ICommand{private IArchitecture mArchitecture;void ICommand.Execute(){OnExecute();}IArchitecture IBelongToArchitecture.GetArchitecture(){return mArchitecture;}void ICanSetArchitecture.SetArchitecture(IArchitecture architecture){mArchitecture = architecture;}protected  abstract void OnExecute();}

查询(Query)

查询模式用于获取数据,用于实现 CQRS 模式中的查询操作。

IQuery接口

IQuery定义了查询的基本行为,包含 Do() 方法,返回查询结果。

 public interface IQuery<TResult> : IBelongToArchitecture, ICanSetArchitecture, ICanGetModel, ICanGetSystem, ICanSendQuery{TResult Do();}

AbstractQuery抽象类

AbstractQuery<T> 提供了查询的基本实现,开发者可以通过继承该类来实现具体的查询逻辑,并在 OnDo() 方法中定义查询逻辑。

 public abstract class AbstractQuery<T> : IQuery<T>{public T Do(){return OnDo();}protected abstract T OnDo();private IArchitecture mArchitecture;public IArchitecture GetArchitecture(){return mArchitecture;}public void SetArchitecture(IArchitecture architecture){mArchitecture = architecture;}}

事件系统(Event System)

事件系统用于模块间的解耦通信,允许通过注册事件监听器和发送事件来实现模块间的消息传递。

旧版事件类

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;[Obsolete("在短学期中发现Event脚本功能不够完善,请换成TypeEventSystem",true)]
public  class Event<T> where T : Event<T>{//这是一个泛型约束,限制了T必须继承或实现Event<T>。换句话说,任何作为T的类型参数必须是Event<T> 的子类(或Event<T> 本身)private static Action mOnEventTrigger;public static void Register(Action OnEvent){mOnEventTrigger += OnEvent;}public static void UnRegister(Action OnEvent){mOnEventTrigger -= OnEvent;}public static void Trigger(){mOnEventTrigger?.Invoke();}}

这个简单的事件系统,提供了注册、注销、触发事件的基本功能。它只能处理无参事件的触发。事件的类型 T 被约束为继承自 Event<T>,这可能会限制事件的多样性与扩展性。并且没有内置的事件生命周期管理机制,需要手动管理事件的注册与注销。当需要处理更复杂的事件时,可能会显得力不从心。

重构后的事件系统允许注册和触发带参数的事件。通过泛型可以支持多种类型的事件处理,增强了代码的可扩展性和适应性。支持通过接口 ITypeEventSystemIUnRegister 进行事件注册和注销的管理,提供了更丰富的事件管理功能。使用 Dictionary<Type, IRegistrations> 来管理不同类型的事件,可以轻松处理多种类型的事件。提供了更复杂的事件注册与注销机制,包括在对象销毁时自动注销事件,避免内存泄漏。提供了 IUnRegister 接口,使得事件的注销更加灵活,可以按需控制事件生命周期。提供了扩展方法 UnRegisterWhenGameObjectDestroyed,可以在 GameObject 销毁时自动注销事件,增强了系统的健壮性。UnRegisterOnDestoryTrigger 通过 HashSet<IUnRegister> 管理事件注销,确保不会因为对象销毁而导致内存泄漏或错误的事件调用。适用于更复杂、更通用的事件管理场景。通过泛型支持各种类型的事件,适合大型项目或需要灵活事件处理的系统。

ITypeEventSystem 接口

ITypeEventSystem 定义了事件系统的行为,支持注册事件监听器、发送事件和注销事件监听器。

public interface ITypeEventSystem
{/// <summary>/// 发送事件/// </summary>/// <typeparam name="T"></typeparam>void Send<T>() where T : new();void Send<T>(T e);/// <summary>/// 注册事件/// </summary>/// <typeparam name="T"></typeparam>/// <param name="onEvent"></param>/// <returns></returns>IUnRegister Register<T>(Action<T> onEvent);/// <summary>/// 销毁事件/// </summary>/// <typeparam name="T"></typeparam>/// <param name="onEvent"></param>void UnRegister<T>(Action<T> onEvent);
}
/// <summary>
/// 注销的接口
/// </summary>
public interface IUnRegister
{void UnRegister();
}

TypeEventSystem 类

TypeEventSystem 是事件系统的具体实现,使用内部的注册表来管理事件和监听器的映射关系。

  public class TypeEventSystem : ITypeEventSystem{interface IRegistrations{}class Registrations<T> : IRegistrations{public Action<T> OnEvent = obj => { };}private Dictionary<Type, IRegistrations> mEventRegistrations = new Dictionary<Type, IRegistrations>();public void Send<T>() where T : new(){var e = new T();Send<T>(e);}public void Send<T>(T e){var type = typeof(T);IRegistrations eventRegistrations;if (mEventRegistrations.TryGetValue(type, out eventRegistrations)){(eventRegistrations as Registrations<T>)?.OnEvent.Invoke(e);}}public IUnRegister Register<T>(Action<T> onEvent){var type = typeof(T);IRegistrations eventRegistrations;if (mEventRegistrations.TryGetValue(type, out eventRegistrations)){}else{eventRegistrations = new Registrations<T>();mEventRegistrations.Add(type, eventRegistrations);}(eventRegistrations as Registrations<T>).OnEvent += onEvent;return new TypeEventSystemUnRegister<T>(){OnEvent = onEvent,TypeEventSystem = this};}public void UnRegister<T>(Action<T> onEvent){var type = typeof(T);IRegistrations eventRegistrations;if (mEventRegistrations.TryGetValue(type, out eventRegistrations)){(eventRegistrations as Registrations<T>).OnEvent -= onEvent;}}}

IOC 容器

IOC 容器用于管理对象的创建和生命周期,通过依赖注入的方式提供实例。这个 IOCContainer 提供了基础的依赖注入功能,可以帮助开发者更好地管理对象的创建和依赖。虽然它非常简单,但在小型项目中已经足够实用。但我觉得对于我如今的项目已经充够了.

IOCContainer 类

IOCContainer 实现了对象的注册和获取功能,通过字典管理类型与实例的映射关系,支持依赖注入。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
namespace FrameworkDesign
{public class IOCContainer{private Dictionary<Type,object> mInstances = new Dictionary<Type,object>();public void Register<T>(T instance){var key = typeof(T);if (mInstances.ContainsKey(key)){mInstances[key] = instance;}else{mInstances.Add(key, instance);}}public T Get<T>() where T : class{var key =typeof(T);if (mInstances.TryGetValue(key, out object retInstance)){return retInstance as T;}return null;}}}

可绑定属性(Bindable Property)

可绑定属性是一种特殊的数据容器,用于将数据变化与 UI 或其他模块的更新逻辑绑定在一起。

BindableProperty`类

BindableProperty<T> 包装了一个值,并提供了 RegisterRegisterWithInitValue 方法,用于注册数据变化监听器。当属性值发生变化时,监听器将被触发,执行相应的更新逻辑。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace FrameworkDesign
{public  class BindableProperty<T>where T : IEquatable<T>{//当你希望比较两个对象是否相等时,通常会使用Equals方法。IEquatable<T>接口允许你为自定义类型提供特定的相等性逻辑,确保在比较两个对象时考虑到特定的属性或字段。//例如,int、string、DateTime等类型都实现了IEquatable<T> 接口,这样可以确保你在使用泛型类时,能够通过T的Equals方法来比较实例。private T mValue = default(T);public T Value {get{return mValue;}set{if(!value.Equals(mValue)) {mValue = value;mOnValueChanged?.Invoke(mValue);}}}public Action<T> mOnValueChanged = (v) => { };public IUnRegister RegisterOnValueChanged(Action<T> onValueChanged){mOnValueChanged += onValueChanged;return new BindablePropertyUnRegister<T>(){BindableProperty = this,OnValueChanged = onValueChanged};}public void UnRegisterOnValueChanged(Action<T> onValueChanged){mOnValueChanged -= onValueChanged;}public class BindablePropertyUnRegister<T1> : IUnRegister where T1 : IEquatable<T1>{public BindableProperty<T1> BindableProperty { get; set; }public Action<T1> OnValueChanged {  get; set; }public void UnRegister(){BindableProperty.UnRegisterOnValueChanged(OnValueChanged);BindableProperty = null;OnValueChanged = null;}}}
}

使用示例

初始化架构

开发者需要定义一个继承自 Architecture<T> 的具体架构类,并实现 Init() 方法,在该方法中注册系统、模型和实用工具。

public class YourProjectArchitecture : Architecture<YourProjectArchitecture>
{protected override void Init(){RegisterSystem(new YourSystem());RegisterModel(new YourModel());RegisterUtility(new YourUtility());}
}

定义系统

public class YourSystem : AbstractSystem
{protected override void OnInit(){// 初始化系统逻辑}
}

定义模型

public class YourModel : AbstractModel
{protected override void OnInit(){// 初始化模型逻辑}
}

发送命令

public class StartCommand : AbstractCommand
{protected override void OnExecute(){var gameSystem = GetSystem<GameSystem>();gameSystem.StartGame();}
}

查询数据

public class GetScoreQuery : AbstractQuery<int>
{protected override int OnDo(){var gameModel = GetModel<GameModel>();return gameModel.Score;}
}

使用事件系统

// 发送事件
SendEvent(new GameOverEvent());// 注册事件监听器
RegisterEvent<GameOverEvent>(OnGameOver);// 事件处理
private void OnGameOver(GameOverEvent e)
{Debug.Log("Game Over");
}

使用可绑定属性

public class YourModel : AbstractModel
{public BindableProperty<int> Score { get; private set; } = new BindableProperty<int>(0);protected override void OnInit(){// 绑定属性变化Score.Register(OnScoreChanged);}private void OnScoreChanged(int newScore){Debug.Log("Score updated: " + newScore);}
}

IStorage工具层MySql方法封装

同样按照依赖倒置原则进行封装

public interface IStorageInMySql:IUtility
{}public class Database:IStorageInMySql
{private static string connstr = "server=127.0.0.1;database=YGFramework;user=root;password=chenyigen;charset=utf8";public static MySqlDataReader ExecuteReader(string sqlString){MySqlConnection connection = new MySqlConnection(connstr);MySqlCommand cmd = new MySqlCommand(sqlString, connection);MySqlDataReader? myReader = null;try{connection.Open();myReader = cmd.ExecuteReader(CommandBehavior.CloseConnection);return myReader;}catch (MySqlException e) // 使用 MySqlException 而不是 SqlException{connection.Close();throw new Exception(e.Message);}finally{if (myReader == null){cmd.Dispose();connection.Close();}}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/830729.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《计算机基础与程序设计》第7周学习总结

学期(2024-2025-1) 学号(20241428) 《计算机基础与程序设计》第7周学习总结 作业信息 |这个作业属于哪个课程|<班级的链接>(如[2024-2025-1-计算机基础与程序设计](https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP|)| |-- |-- | |这个作业要求在哪里|<作…

基于Java+SpringBoot+Mysql在线课程学习教育系统功能设计与实现一

技术点:SpringBoot+SpringDataJPA+Mysql+Freemaker+Bootstrap+JS+CSS+HTML 特色功能:发布课程、学习课程、分享资料、资料讨论等。 部分功能:前台用户信息实体类Entity、新闻信息实体类Entity、课程分类信息实体类Entity、课程信息实体类Entity、角色信息实体类Entity、用户…

『模拟赛』NOIP2024(欢乐)加赛3

『模拟赛记录』NOIP2024(欢乐)加赛3Rank 真欢乐吗, 不过 mission accomplished.A. Sakurako and Water CF2033B *900 byd 还懂难易搭配,不过这个 b 翻译甚至不着重以下主对角线差评,被硬控半个小时,直到手模样例才发觉不对。 读懂题就很简单了,最优一定是找最长的对角线…

权限系统:一文搞懂功能权限、数据权限

大家好,我是汤师爷~ 在权限系统中,权限通常分为两大类:功能权限和数据权限。这两种权限相辅相成,共同决定了用户在系统中可以执行哪些操作、访问哪些信息。 功能权限 1、功能权限是什么 当登录某个系统时,为什么有些功能按钮是灰色的,而有些页面甚至完全不可见?这正是功…

2024.10.30(Maven)

Main放源代码 test放测试代码 pom.xml项目核心配置文件 Maven的主要功能有: 1.提供了一套标准化的项目结构 2.听了一套标准化的构建流程(编译、测试、打包、发布....) 3.提供了一套依赖管理机制

【日记】你们两个熊孩子,不要叫我叔叔啊啊啊啊(1630 字)

正文今天上午起床,窗外地面湿了。瞬间不想出门吃饭了。挣扎了一会儿,还是带着伞出门了。毕竟昨天说了要出门剪头发。花了一个小时吧好像。指定的那个理发店还挺远。理发师都认识我了,笑了笑,问我怎么剪。我还是说只修一下刘海,不要遮住眉毛和眼睛就行,后面和侧边还要继续…

东商项目mysql实例库(dingding)增量备份的实现

一、钉钉库(dingding)实例全量备份 1.机器:cvlink-xxcc-prd1172.16.50.59cvlink-xxcc-prd2172.16.50.60备注:59是主库,60为从库,因为做了主从,所以两个数据库数据是一致的,直接对从库60的实例钉钉进行备份即可。2.对从库60的实例进行备份: 2.1 登录从库:2.2 进行备份…

安娜的档案(Anna’s Archive)官网最新镜像网站入口(实时更新)

安娜的档案(Anna’s Archive)是一个广受欢迎的在线资源平台,汇集了Z-Library、Library Genesis和Sci-Hub等资源,拥有超过2000万册书籍和9000万篇论文。该网站提供多种下载选项,包括快速、慢速、libgen、IPFS和Tor浏览器下载,以满足不同用户的网络和需求。用户可以无需注册…

STM32的内存(堆和栈)

一般来说单片机的内存指的是FLASH和RAM,当在程序中定义了全局变量、局部变量、只读变量等参数时都是会存放到对应的FLASH或者是RAM中。具体对单片机FLASH和RAM的介绍之后再写,这里只对单片机内存分配,对堆和栈以及变量的存储做一个梳理和记录。1、FLASH(0x0800 0000) FLAS…

使用Boost.asio与Boost.beast基于协程连接ws

目录目录 前言 准备工作 实现初始化io_context并监听信号 启动连接ws的线程并启动io_context 建立tcp链接(以下步骤皆位于ws函数中) ws握手 传输数据 效果总结前言 本文主要介绍一个使用Boost.asio和Boost.beast基于协程连接Websocket(ws)的方法。其中C++版本为20,Boost版本…

使用 Let’s Encrypt 免费申请泛域名 SSL 证书,并实现自动续期

使用 Let’s Encrypt 免费申请泛域名 SSL 证书,并实现自动续期 目录使用 Let’s Encrypt 免费申请泛域名 SSL 证书,并实现自动续期🛠️ 环境准备💡 什么是 Let’s Encrypt?🧠 Let’s Encrypt 证书颁发原理1. 域名验证2. 证书生成3. 证书安装4. 自动续期🛠️ Certbot…

Python爬虫学习笔记

目录基础篇:HTTP:HTTP请求:请求行:请求头:请求体:HTTP响应:状态行:响应头:响应体:Requests库:GET请求:POST请求:HTML:HTML网页结构:HTML标签:网页解析:Regular Expression:元字符:量词:正则表达式:Re解析:实战案例:Beautiful Soup:安装:成员属性/函数…