2020-02-13 10:35

EF Core 3.1 取得 IQueryable 的 SQL

private static T getPrivate<T>(this object obj, string privateField)
{
    return (T)obj?.GetType()
        .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)
        ?.GetValue(obj);
}

public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
{
    var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();

    var relationalQueryContext = enumerator.getPrivate<RelationalQueryContext>("_relationalQueryContext");
    var relationalCommandCache = enumerator.getPrivate<RelationalCommandCache>("_relationalCommandCache");

#pragma warning disable EF1001 // Internal EF Core API usage.
    IRelationalCommand command = relationalCommandCache.GetRelationalCommand(relationalQueryContext.ParameterValues);
#pragma warning restore EF1001 // Internal EF Core API usage.

    return command.CommandText;
}
2019-07-22 16:49

C# COM 元件使用 MTA

先前有用到一個通訊用的 COM 元件,因為連線不穩的時候會影響到 Main Thread 造成這個 WinForm UI 卡住,連帶所有 Main Thread 下的其他 Thread 都卡住,最先找到的方法是在 Program Main 上改用 MTAThreadAttribute,的確是可以解決卡住的問題。

但 WPF 就不可以用 MTAThreadAttribute,因為 WPF 必須執行在 STAThread 的環境上,又開始苦惱這個問題了,問題應該還是有解套的辦法的只是知識不足,最後在 WIKI 中看到重要的知識。

WIKI 元件物件模型
一個COM物件只能存在於一個套間。COM物件一經建立就確定所屬套間,並且直到銷毀它一直存在於這個套間。

所以只要用其他 Thread 去建立 COM 元件就不會影響到 Main Thread 了,簡單的解決問題,果然是知識不足。

ActEasyIF actConnection;

var waiter = new AutoResetEvent(false);

new Thread(() =>
{
    /* 建構 COM 元件 */
    _actConnection = new ActEasyIF();

    waiter.Set();
}).Start();

waiter.WaitOne(500); /* 等待建立結束 */
2019-07-22 15:36

C# struct 轉換到 byte array

StructLayout: https://docs.microsoft.com/zh-tw/dotnet/api/system.runtime.interopservices.layoutkind?view=netframework-4.8
Pack: 資料欄位的對齊,這會影響最短欄位的 byte 長度

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct PollResponse
{
    public int AppId;
    public byte Serial;
    public short Station;
}


void Main()
{
    var data = new PollResponse
    {
        AppId = 1,
        Serial = 2,
        Station = 3,
    };

    Type type = typeof(PollResponse);
    int size = Marshal.SizeOf(type);
    var bytes = new byte[size];

    /* struct to byte array */
    IntPtr ptrIn = Marshal.AllocHGlobal(size);
    Marshal.StructureToPtr(data, ptrIn, true);
    Marshal.Copy(ptrIn, bytes, 0, size);
    Marshal.FreeHGlobal(ptrIn);

    BitConverter.ToString(bytes).Dump();
    /* 01-00-00-00 - 02 - 03-00 */


    /* byte array to struct */
    IntPtr ptrOut = Marshal.AllocHGlobal(size);
    Marshal.Copy(bytes, 0, ptrOut, size);
    var result = (PollResponse)Marshal.PtrToStructure(ptrOut, type);
    Marshal.FreeHGlobal(ptrOut);

    result.Dump();
    /* { AppId = 1, Serial = 2, Station = 3 } */
}
2019-07-22 13:52

C# 用 Lambda 設定 MVC Route Constraint

MVC 的 Route Constraint 支援 Regular Pattern (string) 以及 IRouteConstraint,簡單的限制還可以用 Regular 處理,複雜的就需要實作 IRouteConstraint 了,既然都是一次工,索性就想要搭配 Lambda 讓設定可以更彈性的點。


routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    constraints: new { controller = new ValueConstraint(x => x.ToUpper() != "WCF") }
);

public class ValueConstraint : IRouteConstraint
{
    private Func<string, bool> _match;

    public ValueConstraint(Func<string, bool> match)
    {
        _match = match;
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        return _match("" + values[parameterName]);
    }
}


順便增加了 HttpContextBase 的處理
routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    constraints: new { authenticated = new HttpConstraint(x => x.Request.IsAuthenticated) }
);

public class HttpConstraint : IRouteConstraint
{
    private Func<HttpContextBase, bool> _match;

    public HttpConstraint(Func<HttpContextBase, bool> match)
    {
        _match = match;
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        return _match(httpContext);
    }
}
2019-07-21 17:21

C# 讓 Dequeue 更方便的擴充方法

void Main()
{
    var queue = new Queue<int>();

    for (int i = 0; i < 10; i++) { queue.Enqueue(i); }
    string.Join(",", queue).Dump(); /* 0,1,2,3,4,5,6,7,8,9 */


    var take = queue.EnumerateDequeue().Take(4).ToList();
    string.Join(",", take).Dump(); /* 0,1,2,3 */
    string.Join(",", queue).Dump(); /* 4,5,6,7,8,9 */


    var take2 = queue.EnumerateDequeue().Take(40).ToList();
    string.Join(",", take2).Dump(); /* 4,5,6,7,8,9 */
    string.Join(",", queue).Dump(); /* */
}



public static class QueueExtensions
{

   public static IEnumerable<T> EnumerateDequeue<T>(this Queue<T> source)
   {
       while (source.Count > 0) { yield return source.Dequeue(); }
   }

   public static IEnumerable<T> EnumerateDequeue<T>(this ConcurrentQueue<T> source)
   {
       T outValue;
       while (source.TryDequeue(out outValue)) { yield return outValue; }
   }

}
2019-07-21 17:06

C# 在 Enum 上增加附加資訊

C# 的 Enum 是個很方便的類型,如果可以再增加額外的資訊就更方便了,這裡利用 Attribute 去定義 Enum 額外的資訊,再用擴充方法取得 Enum 所屬的資訊。

用 Attribute 來定義有個好處,未來在增減 Enum 時可以一起進行修改,不用擔心會有遺漏而沒修改的問題。

void Main()
{
    PortAreaCode.F1Front.GetFloor().Dump(); /* F1 */
}


public enum PortAreaCode
{
    [AreaMeta("None", 0)]
    None,

    [AreaMeta("F1", 1)]
    F1Front,

    [AreaMeta("F2", 1)]
    F2Front,
}



/// <summary>PortAreaCode 額外附屬資訊定義的 Attribute</summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
class AreaMetaAttribute : Attribute
{
    public string Floor { get; private set; }
    public int WarehouseId { get; private set; }

    public AreaMetaAttribute() : this("None", 0) { }

    public AreaMetaAttribute(string floor, int warehouseId)
    {
        Floor = floor;
        WarehouseId = warehouseId;
    }
}



/// <summary>PortAreaCode 的擴充方法</summary>
public static class PortAreaCodeExtensions
{
    private static AreaMetaAttribute _defaultMeta = new AreaMetaAttribute();

    private static AreaMetaAttribute getMeta(PortAreaCode value)
    {
        FieldInfo field = typeof(PortAreaCode).GetField(value.ToString());
        if(field == null) { return _defaultMeta; }

        var meta = field.GetCustomAttribute<AreaMetaAttribute>();
        return meta ?? _defaultMeta;
    }

    public static string GetFloor(this PortAreaCode value)
    {
        return getMeta(value).Floor;
    }

    public static int GetWarehouseId(this PortAreaCode value)
    {
        return getMeta(value).WarehouseId;
    }
}
2019-07-21 16:29

C# 用 gzip 壓縮字串並取得 base64 字串

這個使用方式的效果是有但書的,當 Source 的重複率不高壓縮的效果就不會好,再加上 base64 就是用可見文字去表示 byte 值,這會讓 base64 後的結果比 byte array 還要長,所以壓縮率沒有到達一定的程度下,輸出反而會比 Source 的字串還要長。

//using System.IO.Compression;


void Main()
{
    string text = "OptionPostal,OptionClassType,OptionTalentItem";
    text.Length.Dump(); /* 45 */

    string compressBase64 = compress(text);
    compressBase64.Length.Dump(); /* 76 */
    compressBase64.Dump(); 
    /* H4sIAAAAAAAEAPMvKMnMzwvILy5JzNHxB3OccxKLi0MqC1Kh/JDEnNS8Es+S1FwAaY6qVC0AAAA= */

    string decompressText = decompress(compressBase64);
    decompressText.Dump(); 
    /* OptionPostal,OptionClassType,OptionTalentItem */

    text = "OptionPostal,OptionClassType,OptionTalentItem,OptionPostal,OptionClassType,OptionTalentItem,OptionPostal,OptionClassType,OptionTalentItem,OptionPostal,OptionClassType,OptionTalentItem";
    text.Length.Dump(); /* 183 */

    compressBase64 = compress(text);
    compressBase64.Length.Dump(); /* 84 */
    compressBase64.Dump(); 
    /* H4sIAAAAAAAEAPMvKMnMzwvILy5JzNHxB3OccxKLi0MqC1Kh/JDEnNS8Es+S1FyowCBQDQBPmlWktwAAAA== */

}


/*壓縮*/
private static string compress(string text)
{
    if (string.IsNullOrEmpty(text)) { return text; }

    byte[] buffer = Encoding.UTF8.GetBytes(text);

    using (var outStream = new MemoryStream())
    using (var zip = new GZipStream(outStream, CompressionMode.Compress))
    {
        zip.Write(buffer, 0, buffer.Length);
        zip.Close();

        string compressedBase64 = Convert.ToBase64String(outStream.ToArray());
        return compressedBase64;
    }
}


/*解壓縮*/
private static string decompress(string compressed)
{
    if (string.IsNullOrEmpty(compressed)) { return compressed; }

    byte[] buffer = Convert.FromBase64String(compressed);

    using (var inStream = new MemoryStream(buffer))
    using (var outStream = new MemoryStream())
    using (var zip = new GZipStream(inStream, CompressionMode.Decompress))
    {
        zip.CopyTo(outStream);
        zip.Close();

        string text = Encoding.UTF8.GetString(outStream.ToArray());
        return text;
    }
}
2019-07-21 15:53

產生 IP v6 的 mask byte array

int length = 121; /* total 128 */

var mask = new byte[16];

for (int i = 0; i < 16; i++)
{
    mask[i] = 0xff;
    if (length > -8) { length -= 8; }
    if (length < 0) { mask[i] = (byte)(mask[i] << -length); }
    /* 當 length 出現負值時代表需要進行位移 */
}

BitConverter.ToString(mask).Dump();
/* FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-80 */