22 changed files with 1272 additions and 435 deletions
@ -1,64 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Text; |
|
||||
|
|
||||
namespace Apewer.Source |
|
||||
{ |
|
||||
|
|
||||
/// <summary></summary>
|
|
||||
public abstract class Accessor<T> where T : class, IDatabase, new() |
|
||||
{ |
|
||||
|
|
||||
private T _source = null; |
|
||||
|
|
||||
private bool _disposed = false; |
|
||||
|
|
||||
/// <summary></summary>
|
|
||||
public Accessor() { } |
|
||||
|
|
||||
/// <summary></summary>
|
|
||||
protected virtual T Source |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
if (_source == null) _source = new T(); |
|
||||
return _source; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary></summary>
|
|
||||
public virtual bool Connected |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
return (_source == null) ? false : _source.Online; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary></summary>
|
|
||||
public virtual bool Disposed |
|
||||
{ |
|
||||
get { return _disposed; } |
|
||||
} |
|
||||
|
|
||||
/// <summary></summary>
|
|
||||
public virtual void Close() |
|
||||
{ |
|
||||
if (_source == null) return; |
|
||||
_source.Close(); |
|
||||
} |
|
||||
|
|
||||
/// <summary></summary>
|
|
||||
public virtual void Dispose() |
|
||||
{ |
|
||||
if (_source != null) |
|
||||
{ |
|
||||
_source.Dispose(); |
|
||||
_source = null; |
|
||||
} |
|
||||
_disposed = true; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
} |
|
@ -0,0 +1,46 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Apewer.Source |
||||
|
{ |
||||
|
|
||||
|
/// <summary>数据库引擎支持 ORM 访问。</summary>
|
||||
|
public interface IOrm : IDatabase |
||||
|
{ |
||||
|
|
||||
|
/// <summary>初始化指定类型,以创建表或增加字段。</summary>
|
||||
|
/// <param name="model">要初始化的类型。</param>
|
||||
|
/// <returns>错误信息。当成功时候返回空字符串。</returns>
|
||||
|
public string Initialize(Type model); |
||||
|
|
||||
|
/// <summary>插入记录。</summary>
|
||||
|
/// <param name="record">要插入的记录实体。</param>
|
||||
|
/// <returns>错误信息。当成功时候返回空字符串。</returns>
|
||||
|
public string Insert(Record record); |
||||
|
|
||||
|
/// <summary>更新记录。</summary>
|
||||
|
/// <param name="record">要插入的记录实体。</param>
|
||||
|
/// <returns>错误信息。当成功时候返回空字符串。</returns>
|
||||
|
public string Update(Record record); |
||||
|
|
||||
|
/// <summary>获取指定类型的主键,按 Flag 属性筛选。</summary>
|
||||
|
/// <param name="model">要查询的类型。</param>
|
||||
|
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
|
||||
|
public Result<List<string>> Keys(Type model, long flag = 0); |
||||
|
|
||||
|
/// <summary>获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。</summary>
|
||||
|
/// <param name="key">目标记录的主键。</param>
|
||||
|
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
|
||||
|
public Result<T> Get<T>(string key, long flag = 0) where T : Record; |
||||
|
|
||||
|
/// <summary>使用指定语句查询,获取查询结果。</summary>
|
||||
|
public Result<List<T>> Query<T>(string sql) where T : Record; |
||||
|
|
||||
|
/// <summary>查询所有记录。</summary>
|
||||
|
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
|
||||
|
public Result<List<T>> Query<T>(long flag = 0) where T : Record; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,965 @@ |
|||||
|
#if EXTRA
|
||||
|
|
||||
|
/* 2021.08.03 */ |
||||
|
|
||||
|
using Apewer; |
||||
|
using Apewer.Source; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Data; |
||||
|
using System.Data.SQLite; |
||||
|
using System.Text; |
||||
|
using System.Data.Common; |
||||
|
//using Mono.Data.Sqlite;
|
||||
|
|
||||
|
namespace Apewer.Source |
||||
|
{ |
||||
|
|
||||
|
/// <summary>用于快速连接 SQLite 数据库的辅助。</summary>
|
||||
|
public class Sqlite : IDatabase, IOrm, IDisposable |
||||
|
{ |
||||
|
|
||||
|
#region 变量定义。
|
||||
|
|
||||
|
private SQLiteConnection _db = null; |
||||
|
|
||||
|
private Timeout _timeout = new Timeout(); |
||||
|
private string _connstring = ""; |
||||
|
private string _path = ""; |
||||
|
private string _pass = ""; |
||||
|
private byte[] _passdata = BinaryUtility.EmptyBytes; |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
#region this
|
||||
|
|
||||
|
private void VarInit(string path, Timeout timeout, string pass, byte[] passData) |
||||
|
{ |
||||
|
_path = (path == null) ? "" : path; |
||||
|
_passdata = (passData == null) ? BinaryUtility.EmptyBytes : passData; |
||||
|
_pass = (pass == null) ? "" : pass; |
||||
|
_timeout = timeout; |
||||
|
} |
||||
|
|
||||
|
/// <summary>连接内存。</summary>
|
||||
|
public Sqlite() => VarInit(Memory, new Timeout(), null, null); |
||||
|
|
||||
|
/// <summary>连接指定文件。</summary>
|
||||
|
public Sqlite(string path) => VarInit(path, new Timeout(), null, null); |
||||
|
|
||||
|
/// <summary>连接指定文件。</summary>
|
||||
|
private Sqlite(string path, byte[] passData) => VarInit(path, new Timeout(), null, passData); |
||||
|
|
||||
|
/// <summary>连接指定文件。</summary>
|
||||
|
public Sqlite(string path, string pass) => VarInit(path, new Timeout(), pass, null); |
||||
|
|
||||
|
/// <summary>连接指定文件。</summary>
|
||||
|
public Sqlite(string path, Timeout timeout) => VarInit(path, timeout, null, null); |
||||
|
|
||||
|
/// <summary>连接指定文件。</summary>
|
||||
|
private Sqlite(string path, Timeout timeout, byte[] pass) => VarInit(path, timeout, null, pass); |
||||
|
|
||||
|
/// <summary>连接指定文件。</summary>
|
||||
|
public Sqlite(string path, Timeout timeout, string pass) => VarInit(path, timeout, pass, null); |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
#region 日志。
|
||||
|
|
||||
|
/// <summary>获取或设置日志记录。</summary>
|
||||
|
public Logger Logger { get; set; } |
||||
|
|
||||
|
private void LogError(string action, Exception ex, string addtion) |
||||
|
{ |
||||
|
var logger = Logger; |
||||
|
if (logger != null) logger.Error(this, "SQLite", action, ex.GetType().FullName, ex.Message, addtion); |
||||
|
} |
||||
|
|
||||
|
private void LogError(string action, string message) |
||||
|
{ |
||||
|
var logger = Logger; |
||||
|
if (logger != null) logger.Error(this, "SQLite", action, message); |
||||
|
} |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
#region 实现接口。
|
||||
|
|
||||
|
/// <summary>数据库是否已经连接。</summary>
|
||||
|
public bool Online { get => _db != null && _db.State == ConnectionState.Open; } |
||||
|
|
||||
|
/// <summary>连接数据库,若未连接则尝试连接。</summary>
|
||||
|
/// <returns>是否已连接。</returns>
|
||||
|
public bool Connect() |
||||
|
{ |
||||
|
if (_db == null) |
||||
|
{ |
||||
|
_db = new SQLiteConnection(); |
||||
|
_db.ConnectionString = ConnectionString; |
||||
|
//if (string.IsNullOrEmpty(_connstring) && string.IsNullOrEmpty(_pass) && (_passdata.Length > 0))
|
||||
|
//{
|
||||
|
// _db.SetPassword(_pass);
|
||||
|
//}
|
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (_db.State == ConnectionState.Open) return true; |
||||
|
} |
||||
|
try |
||||
|
{ |
||||
|
_db.Open(); |
||||
|
switch (_db.State) |
||||
|
{ |
||||
|
case ConnectionState.Open: return true; |
||||
|
default: return false; |
||||
|
} |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
LogError("Connection", ex, _db.ConnectionString); |
||||
|
Close(); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>关闭连接,并释放对象所占用的系统资源。</summary>
|
||||
|
public void Close() |
||||
|
{ |
||||
|
if (_db != null) |
||||
|
{ |
||||
|
//_db.Close();
|
||||
|
_db.Dispose(); |
||||
|
_db = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>关闭连接,释放对象所占用的系统资源,并清除连接信息。</summary>
|
||||
|
public void Dispose() { Close(); } |
||||
|
|
||||
|
/// <summary>查询。</summary>
|
||||
|
public IQuery Query(string sql) => Query(sql, null); |
||||
|
|
||||
|
/// <summary>查询。</summary>
|
||||
|
public IQuery Query(string sql, IEnumerable<IDataParameter> parameters) |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(sql)) return Example.InvalidQueryStatement; |
||||
|
|
||||
|
const string table = "result"; |
||||
|
|
||||
|
var connected = Connect(); |
||||
|
if (!connected) return Example.InvalidQueryConnection; |
||||
|
|
||||
|
var query = new Query(); |
||||
|
try |
||||
|
{ |
||||
|
var command = new SQLiteCommand(); |
||||
|
command.Connection = _db; |
||||
|
command.CommandTimeout = Timeout.Query; |
||||
|
command.CommandText = sql; |
||||
|
if (parameters != null) |
||||
|
{ |
||||
|
foreach (var p in parameters) |
||||
|
{ |
||||
|
if (p != null) command.Parameters.Add(p); |
||||
|
} |
||||
|
} |
||||
|
using (var dataset = new DataSet()) |
||||
|
{ |
||||
|
using (var da = new SQLiteDataAdapter(sql, _db)) |
||||
|
{ |
||||
|
da.Fill(dataset, table); |
||||
|
query.Table = dataset.Tables[table]; |
||||
|
} |
||||
|
} |
||||
|
command.Dispose(); |
||||
|
query.Success = true; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
LogError("Query", ex, sql); |
||||
|
query.Success = false; |
||||
|
query.Exception = ex; |
||||
|
} |
||||
|
return query; |
||||
|
} |
||||
|
|
||||
|
/// <summary>执行单条 Transact-SQL 语句。</summary>
|
||||
|
public IExecute Execute(string sql) => Execute(sql, null); |
||||
|
|
||||
|
/// <summary>执行单条 Transact-SQL 语句,并加入参数。</summary>
|
||||
|
public IExecute Execute(string sql, IEnumerable<IDataParameter> parameters) |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(sql)) return Example.InvalidExecuteStatement; |
||||
|
|
||||
|
var connected = Connect(); |
||||
|
if (!connected) return Example.InvalidExecuteConnection; |
||||
|
|
||||
|
var transaction = _db.BeginTransaction(); |
||||
|
var execute = new Execute(); |
||||
|
try |
||||
|
{ |
||||
|
var command = new SQLiteCommand(); |
||||
|
command.Connection = _db; |
||||
|
command.Transaction = transaction; |
||||
|
command.CommandTimeout = Timeout.Execute; |
||||
|
command.CommandText = sql; |
||||
|
if (parameters != null) |
||||
|
{ |
||||
|
foreach (var p in parameters) |
||||
|
{ |
||||
|
if (p != null) command.Parameters.Add(p); |
||||
|
} |
||||
|
} |
||||
|
execute.Rows += command.ExecuteNonQuery(); |
||||
|
transaction.Commit(); |
||||
|
command.Dispose(); |
||||
|
execute.Success = true; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
try { transaction.Rollback(); } catch { } |
||||
|
LogError("Execute", ex, sql); |
||||
|
execute.Success = false; |
||||
|
execute.Exception = ex; |
||||
|
} |
||||
|
try { transaction.Dispose(); } catch { } |
||||
|
return execute; |
||||
|
} |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
#region 属性。
|
||||
|
|
||||
|
/// <summary>获取当前的 SQLiteConnection 对象。</summary>
|
||||
|
public IDbConnection Connection { get => _db; } |
||||
|
|
||||
|
/// <summary>获取或设置超时。</summary>
|
||||
|
public Timeout Timeout { get => _timeout; set => _timeout = value; } |
||||
|
|
||||
|
/// <summary>获取或设置连接字符串,连接字符串非空时将忽略 Path 属性。数据库在线时无法设置。</summary>
|
||||
|
public string ConnectionString |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(_connstring)) |
||||
|
{ |
||||
|
var temp = new StringBuilder(); |
||||
|
temp.Append("data source='", _path, "'; version=3; "); |
||||
|
if (!string.IsNullOrEmpty(_pass)) temp.Append("password=", _pass, "; "); |
||||
|
return temp.ToString(); |
||||
|
} |
||||
|
else return _connstring; |
||||
|
} |
||||
|
set |
||||
|
{ |
||||
|
if (Online) return; |
||||
|
_connstring = string.IsNullOrEmpty(value) ? "" : value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>获取或设置数据库路径(文件或内存)。数据库在线时无法设置。</summary>
|
||||
|
public string Path |
||||
|
{ |
||||
|
get { return _path; } |
||||
|
set |
||||
|
{ |
||||
|
if (Online) return; |
||||
|
_path = string.IsNullOrEmpty(value) ? "" : value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>获取或设置数据库密码。数据库在线时无法设置。</summary>
|
||||
|
public string Password |
||||
|
{ |
||||
|
get { return _pass; } |
||||
|
set |
||||
|
{ |
||||
|
if (Online) return; |
||||
|
_pass = string.IsNullOrEmpty(value) ? "" : value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>获取或设置数据库密码。数据库在线时无法设置。</summary>
|
||||
|
private byte[] PasswordData |
||||
|
{ |
||||
|
get { return _passdata; } |
||||
|
set |
||||
|
{ |
||||
|
if (Online) return; |
||||
|
_passdata = (value == null) ? BinaryUtility.EmptyBytes : value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>保存当前数据库到文件,若文件已存在则将重写文件。</summary>
|
||||
|
public bool Save(string path) |
||||
|
{ |
||||
|
if (!StorageUtility.CreateFile(path, 0, true)) |
||||
|
{ |
||||
|
LogError("Save", TextUtility.Merge("创建文件 ", path, " 失败。")); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var temp = new Sqlite(path); |
||||
|
var result = Save(temp); |
||||
|
temp.Close(); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/// <summary>保存当前数据库到文件,若文件已存在则将重写文件。</summary>
|
||||
|
public bool Save(string path, string password) |
||||
|
{ |
||||
|
if (!StorageUtility.CreateFile(path, 0, true)) |
||||
|
{ |
||||
|
LogError("Save", TextUtility.Merge("创建文件 ", path, " 失败。")); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var temp = new Sqlite(path, password); |
||||
|
var result = Save(temp); |
||||
|
temp.Close(); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/// <summary>保存当前数据库到目标数据库。</summary>
|
||||
|
public bool Save(Sqlite destination) |
||||
|
{ |
||||
|
var error = Backup(this, destination); |
||||
|
return string.IsNullOrEmpty(error); |
||||
|
} |
||||
|
|
||||
|
/// <summary>加载文件到当前数据库。</summary>
|
||||
|
public bool Load(string path) |
||||
|
{ |
||||
|
var temp = new Sqlite(path); |
||||
|
var result = Load(temp); |
||||
|
temp.Close(); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/// <summary>加载文件到当前数据库。</summary>
|
||||
|
public bool Load(string path, params byte[] pass) |
||||
|
{ |
||||
|
var temp = new Sqlite(path, pass); |
||||
|
var result = Load(temp); |
||||
|
temp.Close(); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/// <summary>加载文件到当前数据库。</summary>
|
||||
|
public bool Load(string path, string pass) |
||||
|
{ |
||||
|
var temp = new Sqlite(path, pass); |
||||
|
var result = Load(temp); |
||||
|
temp.Close(); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/// <summary>加载源数据库到当前数据库。</summary>
|
||||
|
public bool Load(Sqlite source) |
||||
|
{ |
||||
|
var error = Backup(source, this); |
||||
|
return string.IsNullOrEmpty(error); |
||||
|
} |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
#region ORM。
|
||||
|
|
||||
|
/// <summary>查询数据库中的所有表名。</summary>
|
||||
|
public List<string> TableNames() |
||||
|
{ |
||||
|
var list = new List<string>(); |
||||
|
if (Connect()) |
||||
|
{ |
||||
|
var sql = "select name from sqlite_master where type='table' order by name; "; |
||||
|
var query = (Query)Query(sql); |
||||
|
for (int r = 0; r < query.Rows; r++) |
||||
|
{ |
||||
|
var cell = query.Text(r, 0); |
||||
|
if (TextUtility.IsBlank(cell)) continue; |
||||
|
list.Add(cell); |
||||
|
} |
||||
|
query.Dispose(); |
||||
|
} |
||||
|
return list; |
||||
|
} |
||||
|
|
||||
|
/// <summary>查询数据库中的所有视图名。</summary>
|
||||
|
public List<string> ViewNames() |
||||
|
{ |
||||
|
var list = new List<string>(); |
||||
|
if (Connect()) |
||||
|
{ |
||||
|
var sql = "select name from sqlite_master where type='view' order by name; "; |
||||
|
var query = (Query)Query(sql); |
||||
|
for (int r = 0; r < query.Rows; r++) |
||||
|
{ |
||||
|
var cell = query.Text(r, 0); |
||||
|
if (TextUtility.IsBlank(cell)) continue; |
||||
|
list.Add(cell); |
||||
|
} |
||||
|
query.Dispose(); |
||||
|
} |
||||
|
return list; |
||||
|
} |
||||
|
|
||||
|
/// <summary>查询表中的所有列名。</summary>
|
||||
|
public List<string> ColumnNames(string table) |
||||
|
{ |
||||
|
var list = new List<string>(); |
||||
|
if (Connect()) |
||||
|
{ |
||||
|
var t = TextUtility.AntiInject(table); |
||||
|
var sql = TextUtility.Merge("pragma table_info('", TextUtility.AntiInject(t), "'); "); |
||||
|
using (var query = Query(sql) as Query) |
||||
|
{ |
||||
|
for (int r = 0; r < query.Rows; r++) |
||||
|
{ |
||||
|
var cell = query.Text(r, "name"); |
||||
|
if (TextUtility.IsBlank(cell)) continue; |
||||
|
list.Add(cell); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return list; |
||||
|
} |
||||
|
|
||||
|
/// <summary>创建表,不修改已存在表。成功时返回空字符串,发生异常时返回异常信息。</summary>
|
||||
|
public string Initialize(Record model) => model == null ? "参数无效。" : Initialize(model.GetType()); |
||||
|
|
||||
|
/// <summary>创建表,不修改已存在表。成功时返回空字符串,发生异常时返回异常信息。</summary>
|
||||
|
public string Initialize<T>() where T : Record => Initialize(typeof(T)); |
||||
|
|
||||
|
/// <summary>创建表,不修改已存在表。当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。</summary>
|
||||
|
public string Initialize(Type model) |
||||
|
{ |
||||
|
var structure = null as TableStructure; |
||||
|
try { structure = TableStructure.ParseModel(model); } |
||||
|
catch (Exception exception) { return exception.Message; } |
||||
|
|
||||
|
// 连接数据库。
|
||||
|
if (!Connect()) return "连接数据库失败。"; |
||||
|
|
||||
|
// 检查现存表。
|
||||
|
var exists = false; |
||||
|
var tables = TableNames(); |
||||
|
if (tables.Count > 0) |
||||
|
{ |
||||
|
var lower = structure.Table.ToLower(); |
||||
|
foreach (var table in tables) |
||||
|
{ |
||||
|
if (TextUtility.IsBlank(table)) continue; |
||||
|
if (table.ToLower() == lower) |
||||
|
{ |
||||
|
exists = true; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (exists) |
||||
|
{ |
||||
|
return TextUtility.Merge("指定的表已经存在。"); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var sqlcolumns = new List<string>(); |
||||
|
foreach (var column in structure.Columns.Values) |
||||
|
{ |
||||
|
var type = GetColumnDeclaration(column); |
||||
|
if (type == TextUtility.EmptyString) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); |
||||
|
sqlcolumns.Add(type); |
||||
|
} |
||||
|
var sql = TextUtility.Merge("create table [", structure.Table, "](", TextUtility.Join(", ", sqlcolumns), "); "); |
||||
|
var execute = Execute(sql); |
||||
|
if (execute.Success) return TextUtility.EmptyString; |
||||
|
return execute.Error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>插入记录。成功时候返回空字符串,发生异常时返回异常信息。</summary>
|
||||
|
public string Insert(Record record) |
||||
|
{ |
||||
|
if (record == null) return "参数无效。"; |
||||
|
record.FixProperties(); |
||||
|
|
||||
|
var structure = null as TableStructure; |
||||
|
try { structure = TableStructure.ParseModel(record); } |
||||
|
catch (Exception exception) { return exception.Message; } |
||||
|
|
||||
|
var parameters = structure.CreateDataParameters(record, CreateDataParameter); |
||||
|
|
||||
|
var sql = GenerateInsertStatement(structure.Table, (IEnumerable<IDataParameter>)parameters); |
||||
|
|
||||
|
var execute = Execute(sql, parameters); |
||||
|
if (execute.Success && execute.Rows > 0) return TextUtility.EmptyString; |
||||
|
return execute.Error; |
||||
|
} |
||||
|
|
||||
|
/// <summary>更新记录,实体中的 Created 和 Key 属性不被更新。成功时返回空字符串,发生异常时返回异常信息。</summary>
|
||||
|
public string Update(Record record) |
||||
|
{ |
||||
|
if (record == null) return "参数无效。"; |
||||
|
record.FixProperties(); |
||||
|
record.Updated = ClockUtility.LucidNow; |
||||
|
|
||||
|
var structure = null as TableStructure; |
||||
|
try { structure = TableStructure.ParseModel(record); } |
||||
|
catch (Exception exception) { return exception.Message; } |
||||
|
|
||||
|
var parameters = structure.CreateDataParameters(record, CreateDataParameter, "_created", "_key"); |
||||
|
|
||||
|
var sql = GenerateUpdateStatement(structure.Table, record.Key, parameters); |
||||
|
|
||||
|
var execute = Execute(sql, parameters); |
||||
|
if (execute.Success && execute.Rows > 0) return TextUtility.EmptyString; |
||||
|
return execute.Error; |
||||
|
} |
||||
|
|
||||
|
/// <summary>获取具有指定 Key 的记录。</summary>
|
||||
|
public Result<T> Get<T>(string key) where T : Record => Get<T>(key, 0); |
||||
|
|
||||
|
/// <summary>获取具有指定 Key 的记录。</summary>
|
||||
|
public Result<T> Get<T>(string key, long flag) where T : Record |
||||
|
{ |
||||
|
var k = TextUtility.SafeKey(key); |
||||
|
if (TextUtility.IsBlank(k) == false) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var ts = TableStructure.ParseModel(typeof(T)); |
||||
|
var f = (flag == 0) ? TextUtility.EmptyString : TextUtility.Merge(" _flag=", flag.ToString(), " and"); |
||||
|
var query = (Query)Query(TextUtility.Merge("select * from [", ts.Table, "] where", f, " _key='", k, "' limit 1; ")); |
||||
|
var list = query.Fill<T>(); |
||||
|
query.Dispose(); |
||||
|
if (list.Count > 0) return new Result<T>(list[0]); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
return new Result<T>(ex); |
||||
|
} |
||||
|
} |
||||
|
return new Result<T>(new Exception("参数无效。")); |
||||
|
} |
||||
|
|
||||
|
/// <summary>查询多条记录。</summary>
|
||||
|
public Result<List<T>> Query<T>() where T : Record => Query<T>(0); |
||||
|
|
||||
|
/// <summary>查询多条记录。</summary>
|
||||
|
public Result<List<T>> Query<T>(long flag) where T : Record |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var ts = TableStructure.ParseModel(typeof(T)); |
||||
|
var f = (flag == 0) ? TextUtility.EmptyString : TextUtility.Merge(" where _flag=", flag.ToString()); |
||||
|
var query = Query(TextUtility.Merge("select * from [", ts.Table, "]", f, "; ")) as Query; |
||||
|
var list = query.Fill<T>(); |
||||
|
query.Dispose(); |
||||
|
return new Result<List<T>>(list); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
return new Result<List<T>>(ex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>获取按指定语句查询到的所有记录。</summary>
|
||||
|
public Result<List<T>> Query<T>(string sql) where T : Record |
||||
|
{ |
||||
|
using (var query = Query(sql) as Query) |
||||
|
{ |
||||
|
if (query.Exception == null) return new Result<List<T>>(query.Fill<T>()); |
||||
|
else return new Result<List<T>>(query.Exception); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>查询所有有效的 Key 值。</summary>
|
||||
|
public Result<List<string>> Keys<T>() where T : Record => Keys<T>(0); |
||||
|
|
||||
|
/// <summary>查询所有有效的 Key 值。</summary>
|
||||
|
public Result<List<string>> Keys<T>(long flag) where T : Record => Keys(typeof(T), flag); |
||||
|
|
||||
|
/// <summary>查询所有有效的 Key 值。</summary>
|
||||
|
public Result<List<string>> Keys(Type model) => Keys(model, 0); |
||||
|
|
||||
|
/// <summary>查询所有有效的 Key 值。</summary>
|
||||
|
public Result<List<string>> Keys(Type model, long flag) |
||||
|
{ |
||||
|
if (model != null) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var list = new List<string>(); |
||||
|
var ts = TableStructure.ParseModel((Type)model); |
||||
|
var f = (flag == 0) ? TextUtility.EmptyString : TextUtility.Merge(" where _flag=", flag.ToString()); |
||||
|
var query = (Query)Query(TextUtility.Merge("select _key from [", ts.Table, "]", f, "; ")); |
||||
|
for (var r = 0; r < query.Rows; r++) |
||||
|
{ |
||||
|
var cell = TextUtility.Trim(query.Text(r)); |
||||
|
if (cell.Length > 0) list.Add(cell); |
||||
|
} |
||||
|
query.Dispose(); |
||||
|
return new Result<List<string>>(list); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
return new Result<List<string>>(ex); |
||||
|
} |
||||
|
} |
||||
|
return new Result<List<string>>(new Exception("参数无效。")); |
||||
|
} |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
#region static
|
||||
|
|
||||
|
/// <summary>对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。</summary>
|
||||
|
public static string Escape(string text, int bytes = 0) |
||||
|
{ |
||||
|
if (text.IsEmpty()) return ""; |
||||
|
|
||||
|
var t = text ?? ""; |
||||
|
t = t.Replace("\\", "\\\\"); |
||||
|
t = t.Replace("'", "\\'"); |
||||
|
t = t.Replace("\n", "\\n"); |
||||
|
t = t.Replace("\r", "\\r"); |
||||
|
t = t.Replace("\b", "\\b"); |
||||
|
t = t.Replace("\t", "\\t"); |
||||
|
t = t.Replace("\f", "\\f"); |
||||
|
|
||||
|
if (bytes > 5) |
||||
|
{ |
||||
|
if (t.GetBytes(Encoding.UTF8).Length > bytes) |
||||
|
{ |
||||
|
while (true) |
||||
|
{ |
||||
|
t = t.Substring(0, t.Length - 1); |
||||
|
if (t.GetBytes(Encoding.UTF8).Length <= (bytes - 4)) break; |
||||
|
} |
||||
|
t = t + " ..."; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return t; |
||||
|
} |
||||
|
|
||||
|
private static string GetColumnTypeName(ColumnType type) |
||||
|
{ |
||||
|
switch (type) |
||||
|
{ |
||||
|
case ColumnType.Binary: |
||||
|
return "blob"; |
||||
|
case ColumnType.Integer: |
||||
|
return "integer"; |
||||
|
case ColumnType.Float: |
||||
|
return "float"; |
||||
|
case ColumnType.VarChar: |
||||
|
case ColumnType.VarChar255: |
||||
|
case ColumnType.VarCharMax: |
||||
|
return "varchar"; |
||||
|
case ColumnType.Text: |
||||
|
return "text"; |
||||
|
case ColumnType.NVarChar: |
||||
|
case ColumnType.NVarChar255: |
||||
|
case ColumnType.NVarCharMax: |
||||
|
return "nvarchar"; |
||||
|
case ColumnType.NText: |
||||
|
return "ntext"; |
||||
|
default: |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static string GetColumnDeclaration(ColumnAttribute column) |
||||
|
{ |
||||
|
var type = TextUtility.EmptyString; |
||||
|
var length = NumberUtility.RestrictValue(column.Length, 0, 255).ToString(); |
||||
|
switch (column.Type) |
||||
|
{ |
||||
|
case ColumnType.Binary: |
||||
|
type = "blob"; |
||||
|
break; |
||||
|
case ColumnType.Integer: |
||||
|
type = "integer"; |
||||
|
break; |
||||
|
case ColumnType.Float: |
||||
|
type = "real"; |
||||
|
break; |
||||
|
case ColumnType.VarChar: |
||||
|
case ColumnType.VarChar255: |
||||
|
type = TextUtility.Merge("varchar(", length, ")"); |
||||
|
break; |
||||
|
case ColumnType.VarCharMax: |
||||
|
type = TextUtility.Merge("varchar(255)"); |
||||
|
break; |
||||
|
case ColumnType.Text: |
||||
|
type = TextUtility.Merge("text"); |
||||
|
break; |
||||
|
case ColumnType.NVarChar: |
||||
|
case ColumnType.NVarChar255: |
||||
|
type = TextUtility.Merge("nvarchar(", length, ")"); |
||||
|
break; |
||||
|
case ColumnType.NVarCharMax: |
||||
|
type = TextUtility.Merge("nvarchar(255)"); |
||||
|
break; |
||||
|
case ColumnType.NText: |
||||
|
type = TextUtility.Merge("ntext"); |
||||
|
break; |
||||
|
default: |
||||
|
return TextUtility.EmptyString; |
||||
|
} |
||||
|
return TextUtility.Merge("[", (object)column.Field, "] ", type); |
||||
|
} |
||||
|
|
||||
|
/// <summary>创建参数。</summary>
|
||||
|
/// <exception cref="ArgumentNullException"></exception>
|
||||
|
/// <exception cref="InvalidOperationException"></exception>
|
||||
|
public static SQLiteParameter CreateDataParameter(Parameter parameter) |
||||
|
{ |
||||
|
if (parameter == null) throw new InvalidOperationException("参数无效。"); |
||||
|
return CreateDataParameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>创建参数。</summary>
|
||||
|
public static SQLiteParameter CreateDataParameter(string name, ColumnType type, int size, object value) |
||||
|
{ |
||||
|
var n = TextUtility.Trim(name); |
||||
|
if (TextUtility.IsBlank(n)) return null; |
||||
|
|
||||
|
var t = GetColumnTypeName(type); |
||||
|
|
||||
|
var s = size; |
||||
|
switch (type) |
||||
|
{ |
||||
|
case ColumnType.VarChar: |
||||
|
s = NumberUtility.RestrictValue(s, 0, 8000); |
||||
|
break; |
||||
|
case ColumnType.NVarChar: |
||||
|
s = NumberUtility.RestrictValue(s, 0, 4000); |
||||
|
break; |
||||
|
case ColumnType.VarChar255: |
||||
|
case ColumnType.VarCharMax: |
||||
|
case ColumnType.NVarChar255: |
||||
|
case ColumnType.NVarCharMax: |
||||
|
s = NumberUtility.RestrictValue(s, 0, 255); |
||||
|
break; |
||||
|
default: |
||||
|
s = 0; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
var v = value; |
||||
|
if (v is string && v != null && s > 0) |
||||
|
{ |
||||
|
v = TextUtility.RestrictLength((string)v, s); |
||||
|
} |
||||
|
|
||||
|
var p = new SQLiteParameter(); |
||||
|
p.ParameterName = n; |
||||
|
p.TypeName = t; |
||||
|
p.Value = v; |
||||
|
if (s > 0) p.Size = s; |
||||
|
return p; |
||||
|
} |
||||
|
|
||||
|
/// <summary>创建参数。</summary>
|
||||
|
public static SQLiteParameter CreateDataParameter(string name, string type, int size, object value) |
||||
|
{ |
||||
|
var v = value; |
||||
|
if (value is string && value != null && size > 0) |
||||
|
{ |
||||
|
v = TextUtility.RestrictLength((string)value, size); |
||||
|
} |
||||
|
|
||||
|
var p = new SQLiteParameter(); |
||||
|
p.ParameterName = name; |
||||
|
p.TypeName = type; |
||||
|
p.Value = v; |
||||
|
p.Size = size; |
||||
|
return p; |
||||
|
} |
||||
|
|
||||
|
/// <summary>创建参数。</summary>
|
||||
|
public static SQLiteParameter CreateDataParameter(string name, string type, object value) |
||||
|
{ |
||||
|
var p = new SQLiteParameter(); |
||||
|
p.ParameterName = name; |
||||
|
p.TypeName = type; |
||||
|
p.Value = value; |
||||
|
return p; |
||||
|
} |
||||
|
|
||||
|
/// <summary>备份数据库,返回错误信息。</summary>
|
||||
|
public static string Backup(Sqlite source, Sqlite destination) |
||||
|
{ |
||||
|
if (source == null) return "SQLite Backup Failed: Invalid Source"; |
||||
|
if (destination == null) return "SQLite Backup Failed: Invalid Destination"; |
||||
|
if (!source.Connect()) return "SQLite Backup Failed: Invalid Source Connection"; |
||||
|
if (!destination.Connect()) return "SQLite Backup Failed: Invalid Destination Connection"; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
source._db.BackupDatabase(destination._db, "main", "main", -1, null, 0); |
||||
|
return ""; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
return "SQLite Load Failed: " + ex.Message; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>创建参数。</summary>
|
||||
|
public static IDbDataParameter CreateParameter(string field, DbType type, int size, object value) |
||||
|
{ |
||||
|
var p = new SQLiteParameter(); |
||||
|
p.ParameterName = field; |
||||
|
p.DbType = type; |
||||
|
p.Size = size; |
||||
|
p.Value = value; |
||||
|
return p; |
||||
|
} |
||||
|
|
||||
|
/// <summary>创建参数。</summary>
|
||||
|
public static IDbDataParameter CreateParameter(string field, DbType type, object value) |
||||
|
{ |
||||
|
var p = new SQLiteParameter(); |
||||
|
p.ParameterName = field; |
||||
|
p.DbType = type; |
||||
|
p.Value = value; |
||||
|
return p; |
||||
|
} |
||||
|
|
||||
|
/// <summary>整理数据库,压缩未使用的空间。</summary>
|
||||
|
public const string Vacuum = "vacuum"; |
||||
|
|
||||
|
/// <summary>内存数据库的地址。</summary>
|
||||
|
public const string Memory = ":memory:"; |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
#region ORM
|
||||
|
|
||||
|
private static string GetParameterName(string parameter) |
||||
|
{ |
||||
|
var name = TextUtility.AntiInject(parameter, 255); |
||||
|
if (name.StartsWith("@") && name.Length > 1) |
||||
|
{ |
||||
|
name = name.Substring(1, name.Length - 1); |
||||
|
} |
||||
|
return name; |
||||
|
} |
||||
|
|
||||
|
private static string GetParameterName(IDataParameter parameter) |
||||
|
{ |
||||
|
var name = TextUtility.EmptyString; |
||||
|
if (parameter != null) |
||||
|
{ |
||||
|
name = GetParameterName(parameter.ParameterName); |
||||
|
} |
||||
|
return name; |
||||
|
} |
||||
|
|
||||
|
private static List<string> GetParametersNames(IEnumerable<IDataParameter> parameters) |
||||
|
{ |
||||
|
var columns = new List<string>(); |
||||
|
if (parameters != null) |
||||
|
{ |
||||
|
foreach (var parameter in parameters) |
||||
|
{ |
||||
|
var name = GetParameterName(parameter); |
||||
|
var isblank = TextUtility.IsBlank(name); |
||||
|
if (isblank) continue; |
||||
|
columns.Add(name); |
||||
|
} |
||||
|
} |
||||
|
return columns; |
||||
|
} |
||||
|
|
||||
|
private static string GenerateInsertStatement(string table, List<string> columns) |
||||
|
{ |
||||
|
var r = TextUtility.EmptyString; |
||||
|
var t = TextUtility.AntiInject(table, 255); |
||||
|
if (columns != null && !TextUtility.IsBlank(t)) |
||||
|
{ |
||||
|
var count = 0; |
||||
|
var names = new List<string>(); |
||||
|
var values = new List<string>(); |
||||
|
foreach (var column in columns) |
||||
|
{ |
||||
|
//names.Add(TextGenerator.Merge("[", column, "]"));
|
||||
|
names.Add(TextUtility.Merge(column)); |
||||
|
values.Add("@" + column); |
||||
|
count += 1; |
||||
|
} |
||||
|
var sb = new StringBuilder(); |
||||
|
if (count > 0) |
||||
|
{ |
||||
|
sb.Append("insert into [", t, "](", TextUtility.Join(", ", names), ") "); |
||||
|
sb.Append("values(", TextUtility.Join(", ", values), "); "); |
||||
|
} |
||||
|
r = sb.ToString(); |
||||
|
} |
||||
|
return r; |
||||
|
} |
||||
|
|
||||
|
/// <summary>生成 INSERT INTO 语句。表名必须有效,无有效参数时将获取空结果。</summary>
|
||||
|
/// <exception cref="System.ArgumentException"></exception>
|
||||
|
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
|
public static string GenerateInsertStatement(string table, IEnumerable<IDataParameter> parameters) |
||||
|
{ |
||||
|
if (table == null) throw new ArgumentNullException(nameof(table)); |
||||
|
var t = TextUtility.AntiInject(table, 255); |
||||
|
if (TextUtility.IsBlank(t)) throw new ArgumentException("表名无效。", nameof(table)); |
||||
|
|
||||
|
var cs = GetParametersNames(parameters); |
||||
|
if (cs.Count < 1) return TextUtility.EmptyString; |
||||
|
|
||||
|
return GenerateInsertStatement(t, cs); |
||||
|
} |
||||
|
|
||||
|
private static string GenerateUpdateStatement(string table, string key, List<string> columns) |
||||
|
{ |
||||
|
var result = TextUtility.EmptyString; |
||||
|
var t = TextUtility.AntiInject(table, 255); |
||||
|
var k = TextUtility.AntiInject(key, 255); |
||||
|
if (columns != null && !TextUtility.IsBlank(t) && !TextUtility.IsBlank(k)) |
||||
|
{ |
||||
|
var items = new List<string>(); |
||||
|
foreach (var column in columns) |
||||
|
{ |
||||
|
items.Add(TextUtility.Merge("[", column, "]=@", column)); |
||||
|
} |
||||
|
if (items.Count > 0) |
||||
|
{ |
||||
|
result = TextUtility.Merge("update [", t, "] set ", TextUtility.Join(", ", items), " where [_key]='", k, "'; "); |
||||
|
} |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/// <summary>生成 UPDATE 语句,键字段名为“_key”。表名必须有效,键值必须有效,无有效参数时将获取空结果。</summary>
|
||||
|
/// <exception cref="System.ArgumentException"></exception>
|
||||
|
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
|
public static string GenerateUpdateStatement(string table, string key, IEnumerable<IDataParameter> parameters) |
||||
|
{ |
||||
|
if (table == null) throw new ArgumentNullException("argTable"); |
||||
|
var t = TextUtility.AntiInject(table, 255); |
||||
|
if (TextUtility.IsBlank(t)) throw new ArgumentException("表名无效。", nameof(table)); |
||||
|
|
||||
|
if (key == null) throw new ArgumentNullException("argKey"); |
||||
|
var k = TextUtility.AntiInject(key, 255); |
||||
|
if (TextUtility.IsBlank(k)) throw new ArgumentException("键值无效。", nameof(key)); |
||||
|
|
||||
|
var columns = GetParametersNames(parameters); |
||||
|
if (columns.Count < 1) return TextUtility.EmptyString; |
||||
|
|
||||
|
return GenerateUpdateStatement(t, k, columns); |
||||
|
} |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
#endif
|
@ -1,34 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Text; |
|
||||
|
|
||||
namespace Apewer.Web |
|
||||
{ |
|
||||
|
|
||||
/// <summary></summary>
|
|
||||
[Serializable] |
|
||||
public sealed class ApiMime |
|
||||
{ |
|
||||
|
|
||||
/// <summary></summary>
|
|
||||
public string Extension { get; set; } |
|
||||
|
|
||||
/// <summary></summary>
|
|
||||
public string ContentType { get; set; } |
|
||||
|
|
||||
/// <summary>缓存过期时间,单位为秒,默认值 0 为不缓存(立即过期)。</summary>
|
|
||||
public int Expires { get; set; } |
|
||||
|
|
||||
/// <summary></summary>
|
|
||||
public ApiMime() { } |
|
||||
|
|
||||
internal ApiMime(string ext, string type, int expires) |
|
||||
{ |
|
||||
Extension = ext; |
|
||||
ContentType = type; |
|
||||
Expires = expires; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
} |
|
@ -0,0 +1,28 @@ |
|||||
|
<Project> |
||||
|
|
||||
|
<!-- 生成 --> |
||||
|
<PropertyGroup> |
||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
||||
|
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile> |
||||
|
<LangVersion>latest</LangVersion> |
||||
|
<NoWarn>CS0108,CS0162,CS0414,CS0612,CS0618,CS0649,CS1589,CS1570,CS1572,CS1573,CS3019,CS3021</NoWarn> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<!-- 程序集信息 --> |
||||
|
<PropertyGroup> |
||||
|
<Product>Apewer Libraries</Product> |
||||
|
<Version>6.3.4</Version> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<!-- NuGet --> |
||||
|
<PropertyGroup Condition="'$(Configuration)'=='Release'"> |
||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> |
||||
|
<IsPackable>true</IsPackable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<!-- Debug --> |
||||
|
<PropertyGroup Condition="'$(Configuration)'=='Debug'"> |
||||
|
<DefineConstants>DEBUG;TRACE;$(DefineConstants);$(AdditionalConstants)</DefineConstants> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
</Project> |
@ -1,6 +1,16 @@ |
|||||
|
|
||||
### 最新提交 |
### 最新提交 |
||||
|
|
||||
|
### 6.3.4 |
||||
|
- Logger:支持指定文件路径; |
||||
|
- Models:StringPairs 支持通过构造函数设置初始容量; |
||||
|
- Source:去除 Attribute 的 Lock 方法,不再锁定属性; |
||||
|
- Source:增加 ORM 接口,统一方法; |
||||
|
- Web:增加 HTTP 头,应对 Edge 浏览器的检查; |
||||
|
- Web:新增 Response.Binary 重载,支持指定文件路径; |
||||
|
- Web:ApiOptions 支持设置 X-Content-Type-Options; |
||||
|
- Web:Staitc 控制器对部分纯文本文件的 Content-Type 加入“utf-8”。 |
||||
|
|
||||
### 6.3.3 |
### 6.3.3 |
||||
- 全局:恢复 .NET Standard 2.0 的输出,用于支持 .NET Framework 4.6.1; |
- 全局:恢复 .NET Standard 2.0 的输出,用于支持 .NET Framework 4.6.1; |
||||
- StorageUtility:优化了 ReadFile 的内存占用; |
- StorageUtility:优化了 ReadFile 的内存占用; |
Loading…
Reference in new issue