C#에서 LINQ와 람다(Lambda)식에 대해 알아봅니다.

 

 

 

1. LINQ란?

 

LINQ (Language-Integrated Query)는 C# 언어에 통합된 쿼리 언어입니다. 이는 데이터 소스에서 데이터를 쿼리하고 조작하는 데 사용됩니다.

 

LINQ를 사용하면 배열, 컬렉션, 데이터베이스, XML 등과 같은 다양한 데이터 소스에서 데이터를 추출하고 변환할 수 있습니다.


일반적인 LINQ 쿼리는 from, where, select와 같은 키워드를 사용하여 데이터를 선택하고 필터링하는 데에 사용됩니다.

 

int[] numbers = [ 0, 1, 2, 3, 4, 5, 6 ];

// LINQ를 이용해 짝수를 쿼리.
var evenNumbers = from num in numbers
                  where num % 2 == 0
                  select num;

 

 

 

2. 람다식(Lambda Expressions)이란?

 

람다식은 익명 함수를 간결하게 표현하는 방법입니다. 주로 LINQ와 함께 사용되며, 간단한 인라인 함수를 정의할 때 효과적입니다.


람다식의 일반적인 구문은 input => expression 형태로 표현됩니다. 여기서 input은 매개변수를 나타내고, expression은 함수의 몸체를 나타냅니다.


람다식은 더 간결하게 코드를 작성할 수 있게 해 주며, LINQ에서는 특히 읽기 쉽고 간편한 필터 및 프로젝션 작업에 자주 사용됩니다.

 

// LINQ에 람다식을 함께 사용.
var evenNumbers = numbers.Where(num => num % 2 == 0);

 

 

 

3. LINQ와 람다식

 

LINQ와 람다식을 조합하여 사용하면 코드를 간결하게 유지하면서도 더 복잡한 조건이나 작업을 처리할 수 있습니다.

 

두 가지를 조합하면 다음과 같은 이점을 얻을 수 있습니다.

1. 람다식은 코드를 더 간결하게 만들어주기 때문에 LINQ와 함께 사용하면 코드가 간편하고 가독성이 높아집니다.

2. 람다식을 사용하면 더 복잡한 조건을 지정할 수 있고, 필요에 따라 쉽게 추가 또는 수정할 수 있습니다.

3. LINQ 메서드와 람다식을 함께 사용하면 메서드 체이닝을 통해 여러 작업을 한 줄로 연결할 수 있습니다.

4. 간단한 연산이나 프로젝션을 람다식 내에서 인라인으로 정의할 수 있어 코드를 더 직관적으로 만들 수 있습니다.

 

 

 

 

 

 

반응형

 

 

EF Core에서 Postgres 사용 시 발생하는 Cannot write DateTime with Kind=UTC to PostgreSQL type 'timestamp without time zone'  에러를 해결하는 방법에 대해 알아봅니다.

 

 

 

현상

 

public class SomeClass
{
    [Column("created_on")]
    public DateTime? created_on { get; set; }
}

 

위와 같이 nullable DateTime 값을 수정하고자 할 때 Cannot write DateTime with Kind=UTC to PostgreSQL type 'timestamp without time zone' 오류가 발생.

 

 

 

수정

 

직접 setter를 구현해 명시적으로 DateTime.Kind를 설정합니다.

 

public class SomeClass{
    [Column("created_on")]
    private DateTime? _created_on
    public DateTime? created_on 
    {
        get
        {
            return this._created_on;
        }
        set 
        {
            this._created_on = DateTime.SpecifyKind(value, DateTimeKind.Utc); 
        }
    }
}

 

위와 같이 수정하면 정상적으로 nullable DateTime 컬럼이 수정됩니다.

 

 

 

 

 

반응형

 

이전에 프로토타입으로 작성한 프로젝트를 모놀리스에서 MSA로 리팩터링 하려고 했습니다. 이를 위해 ORM을 비교하던 중 Dapper와 EF를 두고 고민했는데 고려한 내용을 정리해 둡니다.

 

 

 

주요 차이점.

 

가장 큰 비교 요인으로 꼽히는 것은 성능입니다. Dapper는 EF에 비해 많은 기능을 희생했지만 그만큼 더 빠르다고 알려져 있습니다. 반대로 EF는 성능을 희생시키고 Dapper에서 지원하지 않는 어려운 기능들을 수행해 냅니다.

 

EF를 채택하는 주요한 기능은 Linq입니다. Linq로 인해 런타임에 SQL로 변환됩니다. EF의 Linq 지원은 실제로 IMO 채택을 주도한 주요한 요인입니다.

 

Dapper는 간단하면서 강력합니다. 그야말로 Simple is fast를 추구하는 것 같았습니다. EF에 비하면 초라한 기능이지만 성능면에서는 뛰어납니다. 

 

반면에 EF는 Linq를 지원하고 변경 감지 기능이 있어 dbContext.SaveChanges()를 통해 모든 업데이트를 한 번에 처리할 수 있습니다. 상속도 내장되어 있고 분할 쿼리, 전역 쿼리 필터, 전체 트랜잭션 관리, 마이그레이션 등 수많은 기능을 포함하고 있습니다.

 

Dapper는 개발 시 개발자가 raw 쿼리를 직접 작성해야 합니다. 이는 단점으로 보일 수 있지만 장점으로 작용할 수 도 있습니다.

 

 

 

EF의 성능 개선.

 

MS에 따르면 EF 7에서 데이터베이스에 대한 왕복 횟수를 줄이고 보다 효율적인 SQL을 생성하도록 변경되어 SaveChanges와 SaveChangesAsync의 성능이 일부 시나리오에서 EF 6에 비해 4배 빨라졌다고 게시하였습니다.

 

EF 6.0의 글에선 EF 6의 성능은 업계 표준 TechEmpower Fortunes 벤치마크에서 5.0에 비해 70% 더 빠르다고 게시한 바 있으며 벤치마크 코드, dotNET 런타임 등의 개선을 포함하여 전체 성능이 개선되어 EF Core 6.0 자체는 쿼리 실행 속도가 31% 더 빨라졌다고 게시한 바 있습니다.

 

이렇듯 EF는 기존에 Dapper에 비해 성능이 좋지 않아 지속적으로 성능을 개선시켜 나아가고 있습니다. 추후에는 더 나은 성능 개선을 바랄 수 있을 것으로 생각됩니다.

 

 

 

Dapper의 추가 라이브러리.

 

Dapper는 성능을 위해 많은 기능을 희생했습니다. 하지만 그렇다고 희생한 기능을 사용하지 못하는 것은 아닙니다. Dapper는 새로운 기능을 제공하는 확장 라이브러리가 있습니다. 

 

이러한 라이브러리들은 Dapper에서 기본적으로 제공하지 않는 기능을 지원해 유용하게 사용할 수 있으나 다른 라이브러리를 사용하면 Dapper에서 제공한 성능 벤치마크의 결과가 바뀔 수 있음을 유념해야 합니다.

 

 

 

결론.

 

다양한 레퍼런스를 찾아본 결과 결국 EF 7을 사용하기로 결정했습니다.

 

공식적으로 MS에서는 EF를 지원하는 것도 한몫했습니다. EF는 앞으로도 dotNet 버전과 함께 개선될게 분명하며 시간이 지나 신규 버전이 나올수록 개선되는 성능과 최신 벤치마크 테스트 결과 눈에 띄게 차이 나지 않는 성능으로 인해 딱히 Dapper를 사용해야 할 필요성을 느끼지 못했습니다.

 

다만 수많은 기능과 빠르게 변하는 만큼 러닝커브는 감내해야 할 사항입니다.

 

 

 

 

 

참조:

Is Dapper better than Entity Framework?

Using Entity Framework Core and Dapper in ASP.NET Core – Safe Transactions

Microsoft Claims Entity Framework Core 7 Faster When Saving Changes

The Big Fight — Dapper vs Entity Framework 6 Detailed Benchmark

Announcing Entity Framework Core 6.0 Preview 4: Performance Edition

Entity Framework Core 7 (EF7) is available today

 

 

반응형

 

C# MongoDB MongoDB Driver를 구식 버전에서 업그레이드 함에 따라 변경된 문법을 정리.

 

 

MongoClient

// Old
MongoClient client = new MongoClient("CONNECTION_STRING");
// New: Same syntax.
MongoClient client = new MongoClient("CONNECTION_STRING");

 

 

MongoServer

//Old
MongoServer server = client.GetServer();
//New: MondoServer is deprecated.

 

 

MongoDatabase

// Old
MongoDatabase db = server.GetDatabase("DATABASE_NAME");
// New
IMongoDatabase db = client.GetDatabase("DATABASE_NAME");

 

 

Run Ping

// Old
db.RunCommand("ping");
// New
db.RunCommand<BsonDocument>("{'ping': 1}");

 

 

MongoCollection

// Old
MongoCollection mongoCollection = mongoDatabase.GetCollection<YourDocumentClass>("COLLECTION NAME");
// New
IMongoCollection<YourDocumentClass> mongoCollection = mongoDatabase.GetCollection<YourDocumentClass>("COLLECTION NAME");

 

 

Check the collection is exists

// Old
mongoCollection.Exists();
// New
if(mongoCollection != null)  {}

 

 

Check the index is exists

// Old
mongoCollection.IndexExistsByName(collectionIndex);
// New
listIndexes.SelectMany(idx => idx.Elements).Where(elem => elem.Name.Contains("name")).Select(name => name.Value.ToString()).Contains("INDEX_NAME");

 

 

Create a new index

// Old
IndexKeysBuilder<YourDocumentClass> keys = IndexKeys<YourDocumentClass>.Ascending(x => x.f1).Descending(x => x.f2);
IndexOptionsBuilder options = IndexOptions.SetName("INDEX_NAME");
mongoCollection.CreateIndex(keys, options);
// New
CreateIndexModel<YourDocumentClass> indexModel = new CreateIndexModel<YourDocumentClass>( 
    Builders<YourDocumentClass>.IndexKeys.Ascending(x => x.f1).Descending(x => x.f2),
    new CreateIndexOptions(){ Name: "INDEX_NAME"})
);
mongoCollection.Indexes.CreateOne(indexModel);

 

 

Find Query - FindOne

// Old
var findQuery = Query<YourDocumentClass>.EQ(x => x.f1, "VALUE");
YourDocumentClass doc = collection.FindOneAs<YourDocumentClass>(findQuery);
// New
var findQuery = Builders<YourDocumentClass>.Filter.Eq(x => x.f1, "VALUE");
List<YourDocumentClass> listYourDocumentClass = collection.Find(findQuery).Limit(1).ToList();
YourDocumentClass doc = listYourDocumentClass[0];

 

 

Update Query

// Old
var updateValues = Update<YourDocumentClass>.Set(x => x.f1, "VALUE");
WriteConcernResult result = collection.Update(findQuery, updateValues, UpdateFlags.Multi)
// New
var updateQuery = Builders<YourDocumentClass>.Update.Set(x => x.f1, "VALUE");
collection.UpdateMany(findQuery, updateQuery);

 

 

Delete Query

// Old
var query = Query<YourDocumentClass>.EQ(x => x.f1, "VALUE");
collection.Remove(query);
// New
var findQuery = Builders<YourDocumentClass>.Filter.Eq(x => x.f1, "VALUE");
collection.DeleteMany(findQuery);

 

 

Insert Query

// Old
collection.Insert(YourDocumentClass);
// New
collection.InsertOne(YourDocumentClass);

 

 

 

 

 

 

반응형

 

Entity Framework를 사용해 JOIN 쿼리문을 수행하는 방법에 대해 알아봅니다.

 

 

 

0. 소스코드

 

소스코드는 다음 링크에서 확인할 수 있습니다: https://github.com/smoh-dev/EF_Join

 

GitHub - smoh-dev/EF_Join: https://smoh.tistory.com/471

https://smoh.tistory.com/471. Contribute to smoh-dev/EF_Join development by creating an account on GitHub.

github.com

 

 

 

1. DB 준비

 

MySQL을 설치 한 뒤 다음과 같은 쿼리문을 수행해 테이블을 생성합니다.

 

#Create user table
CREATE TABLE `dev_db`.`tbl_user` (
  `key` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(64) NULL,
  PRIMARY KEY (`key`));

#Create order table.
CREATE TABLE `dev_db`.`tbl_order` (
  `key` INT NOT NULL AUTO_INCREMENT,
  `user_key` INT NOT NULL,
  `code` VARCHAR(128) NOT NULL,
  PRIMARY KEY (`key`),
  INDEX `fk_user_key_idx` (`user_key` ASC),
  CONSTRAINT `fk_user_key`
    FOREIGN KEY (`user_key`)
    REFERENCES `dev_db`.`tbl_user` (`key`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION);

#Insert default data.
INSERT INTO `dev_db`.`tbl_user`(`name`) VALUES ('smoh');
INSERT INTO `dev_db`.`tbl_order`(`user_key`, `code`) VALUES (1, 'my-custom-order-id');

 

 

 

 

2. 프로젝트 생성.

 

편의를 위해 간단한 프로젝트를 생성합니다.

 

 

 

 

 

3. NuGet 패키지 설치.

 

다음 NuGet 패키지를 설치합니다.

 

 

 

 

4. Table Model 생성

 

앞서 생성한 테이블대로 모델을 생성합니다.

 

//TableUser
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace my_test_api.Model
{
    [Table("tbl_user")]
    public class TableUser
    {
        [Key]
        [Column("key")]
        public int Key { get; set; }
        [Column("name")]
        public string Name { get; set; }
    }
}

//TableOrder
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace my_test_api.Model
{
    [Table("tbl_order")]
    public class TableOrder
    {
        [Key]
        [Column("key")]
        public int Key { get; set; }
        [Column("user_key")]
        public int UserKey { get; set; }
        [Column("code")]
        public string Code { get; set; }
    }
}

 

 

 

5. DB Context 생성

 

이제 DB Context를 생성해 서비스에 연결합니다.

 

//DBContext
using Microsoft.EntityFrameworkCore;
using my_test_api.Model;
namespace my_test_api
{
    public class DBContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder options)
        {
            var connectionString = "server=localhost; database=dev_db; user=dev; password=devpass";
            options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
        }
        public DbSet<TableUser> User { get; set; }
        public DbSet<TableOrder> Order { get; set; }
    }
}

 

//Program.cs
using my_test_api;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

//Add DBContext
builder.Services.AddSingleton<DBContext>();

var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();

 

 

 

6. 컨트롤러 작성

 

다음 코드와 같이 간단한 컨트롤러를 작성합니다.

 

//UserOrderController.cs
using Microsoft.AspNetCore.Mvc;
using my_test_api.Model;
namespace my_test_api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UserOrderController : ControllerBase
    {
        DBContext dbContext;
        public UserOrderController(DBContext dBContext)
        {
            this.dbContext = dBContext;
        }
        // GET api/<UserOrderController>/1
        [HttpGet("{id}")]
        public string Get(int id)
        {
            var query = (from u in dbContext.Set<TableUser>()
                        join o in dbContext.Set<TableOrder>()
                        on u.Key equals o.UserKey
                        where u.Key == id
                        select new { u, o }).SingleOrDefault();
            string result = $"UserKey: {query.u.Key}, UserName: {query.u.Name}, OrderKey: {query.u.Key}, OrderCode: {query.o.Code}";
            return result;
        }
    }
}

 

내부의 query를 통해 DB Table Join 작업을 수행합니다.

 

 

 

7. 테스트

 

이제 Swagger를 열고 테스트를 진행해 봅시다.

 

 

쿼리가 정상적으로 동작해 데이터가 출력되는 것을 확인할 수 있습니다.

 

 

 

 

 

반응형

 

 

dotNet 6.0 프로젝트에서 EntityFramework를 사용해 MariaDB에 연결하는 방법에 대해 알아봅니다.

 

 

 

1. NuGet 패키지 설치.

 

다음 NuGet 패키지를 설치합니다.

 

 

 

 

2. DB 테이블 생성

 

DB에 접속해 다음 예시와 같이 테이블을 생성합니다.

 

CREATE TABLE `dev_db`.`users` (
  `Key` INT NOT NULL AUTO_INCREMENT,
  `Id` VARCHAR(128) NOT NULL,
  `Name` VARCHAR(64) NOT NULL,
  PRIMARY KEY (`Key`),
  UNIQUE INDEX `Key_UNIQUE` (`Key` ASC) VISIBLE,
  UNIQUE INDEX `Id_UNIQUE` (`Id` ASC) VISIBLE);

 

 

 

3. DBContext 생성

 

다음 예시와 같이 Model과 DbContext를 생성합니다.

 

//Model: User
    public class User
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public User(string id, string name)
        {
            this.Id = id;
            this.Name = name;
        }
    }
//DbContext: UserContext
    public class UserContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder options)
        {
            var connectionString = "server=localhost; database=dev_db; user=dev; password=devpass"; 
            options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
        }
    }

 

 

 

4. User 추가

 

다음 예시 코드를 실행해 Users 테이블에 User를 추가합니다.

 

// Insert a new user to users table.
using (var context = new UserContext())
{
    context.Users.Add(new User("testID", "testName"));
    context.SaveChanges();
}

 

 

DB에 정상적으로 데이터가 추가된 것을 확인할 수 있습니다.

 

 

 

 

 

반응형

 

어느 날 프로토타입 제작을 위해 VS2022에서 새로운 클래스를 생성했는데 기본 한정자가 public에서 internal로 변경되었는지 internal로 생성이 되더군요. 이 기회에 C#에서 사용하고 있는 액세스 한정자에 대해 알아보고 정리해 둡니다.

 

 

 

1. 액세스 한정자 - Access Modifiers

 

모든 형식과 형식 멤버에는 접근성 수준이 있습니다. 접근성 수준은 어셈블리 또는 다른 어셈블리의 다른 코드에서 사용할 수 있는지 여부를 제어합니다. 어셈블리는 단일 컴파일에서 하나 이상의. cs 파일을 컴파일하여 만든. dll 또는. exe입니다. 선언할 때 형식 또는 멤버의 액세스 가능성을 지정하려면 다음과 같은 액세스 한정자를 사용합니다.

 

쉽게 설명하면 한 클래스에서 다른 클래스의 멤버 변수나 함수에 접근할 때 그 접근이 가능한지 여부를 결정짓는 게 액세스 한정자입니다.

 

 

 

2. 액세스 한정자의 종류

 

C#에서 액세스 한정자는 6가지가 있습니다. 6가지는 다음과 같습니다.

  • public: 형식 또는 멤버는 동일한 어셈블리 또는 이를 참조하는 다른 어셈블리의 다른 코드에서 액세스 할 수 있습니다. 형식의 public 멤버에 대한 액세스 가능성 수준은 형식 자체의 액세스 가능성 수준에 의해 제어됩니다.
  • private: 형식 또는 멤버는 동일한 클래스 또는 구조체의 코드에서만 액세스 할 수 있습니다.
  • protected: 유형 또는 멤버는 동일한 클래스 또는 해당 클래스에서 파생된 클래스의 코드에서만 액세스 할 수 있습니다.
  • internal: 동일한 어셈블리의 모든 코드에서 형식 또는 멤버에 액세스 할 액세스 할 수 있지만 다른 어셈블리에서는 액세스 할 수 없습니다. 즉, 내부 형식이나 멤버는 동일한 컴파일의 일부인 코드에서 액세스 할 수 있습니다.
  • protected internal: 형식 또는 멤버는 선언된 어셈블리의 모든 코드에서 또는 다른 어셈블리의 파생 클래스 내에서 액세스 할 수 있습니다.
  • private protected: 포함하는 어셈블리 내에서 선언된 클래스에서 파생된 형식에서 형식 또는 멤버에 액세스할 수 있습니다.

해당 기능을 표로 정리하면 다음과 같습니다.

 

 

 

더욱더 자세한 내용은 다음 페이지를 참고하시면 좋습니다: Access Modifiers (C# Programming Guide)

 

Access Modifiers - C# Programming Guide

All types and type members in C# have an accessibility level which controls whether they can be used from other code. Review this list of access modifiers.

docs.microsoft.com

 

 

 

 

 

 

 

반응형

 

Cannot load Counter Name data because an invalid index '' was read from the registry. 오류가 발생한 경우 해결하는 방법에 대해 알아봅니다.

 

 

 

1. 현상

 

C# 프로그램을 실행하는데 "Cannot load Counter Name data because an invalid index '' was read from the registry."와 같은 에러가 발생하며 실행되지 않습니다.

 

 

 

2.  수정

 

cmd를 열어 다음 명령어를 수행합니다.

lodctr /r

 

해당 명령어를 수행했을 때 만약 다음과 같은 에러가 발생하면 다시 한번 수행해 줍니다.

ERROR: UNABLE TO REBUILD PERFORMANCE COUNTER SETTING FROM SYSTEM BACKUP STORE. ERROR CODE IS 2

 

다시 동일한 명령어를 수행하면 다음과 같이 정상적인 메시지가 출력됩니다.

 

프로그램을 다시 실행시키면 정상적으로 수행되는 것을 확인할 수 있습니다.

 

 

 

 

 

반응형

 

C#에서 Hostname을 사용해 IP주소를 가져오는 방법에 대해 알아봅니다.

 

 

string hostname = "smoh-dev";
IPAddress[] listIPAddress = Dns.GetHostAddresses(hostname);
IPAddress[] listIPV4 = listIPAddress.Where(x=>x.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork).ToArray();
Console.WriteLine($"Input Hostname: {hostname}");
Console.WriteLine($"IP Address: {listIPAddress[0]}");

 

소스코드는 간단합니다. Hostname을 입력받고 Hostname의 모든 IP 주소를 가져옵니다. 그 후 Linq를 이용해 가져온 IP중에서 IPv4 주소만 찾아옵니다. 

 

마지막으로 찾은 IPv4 주소중 첫번째 항목을 출력합니다. 위 코드를 실행하면 다음과 같은 결과를 얻을 수 있습니다.

 

 

해당 소스코드는 다음 링크에서도 확인할 수 있습니다: smoh-tistory/GetIpByHostname

 

smoh-tistory/GetIpByHostname

Contribute to smoh-tistory/GetIpByHostname development by creating an account on GitHub.

github.com

 

 

 

 

반응형

 

C# MongoDB Driver 2.0+를 사용하다 발생한 "Element 'key' does not match any field or property of class" 오류를 처리하는 방법에 대해 알아봅니다.

 

 

 

1. 현상.

 

FindOneAndUpdate 작업에 IsUpsert 옵션을 true로 설정해 둔 채 작업을 수행하다 에러가 발생했습니다.

 

신규 Insert시에는 문제가 없었으나 Update시에 발생한 문제였으며 예외 메시지는 다음과 같았습니다.

 

Element '{FIELD}' does not match any field or property of class {PROJECT}.MongoDB.Documents.{CLASS}.

 

 

 

2. 수정.

 

클래스 선언 위에 "[BsonIgnoreExtraElements]"를 추가해 줍니다.

 

이후 FindOneAndUpdate 작업을 다시 수행하면 정상적으로 수행 됩니다.

 

 

 

References: Element 'Id' does not match any field or property of class

 

Element 'Id' does not match any field or property of class

I got the result from the collection in MongoDB, the structure is the same as below [DataContract] public class Father { [BsonId] [DataMember] public MongoDB.Bson.ObjectId _id { get; s...

stackoverflow.com

 

반응형

+ Recent posts