데이터 입력 후 문자열을 트리밍하는 가장 좋은 방법입니다.사용자 정의 모델 바인더를 작성해야 합니까?
ASP를 사용하고 있습니다.NET MVC와 나는 사용자가 입력한 모든 문자열 필드가 데이터베이스에 삽입되기 전에 트리밍되기를 원합니다.또한 데이터 입력 양식이 많기 때문에 사용자가 제공하는 모든 문자열 값을 명시적으로 트리밍하는 대신 모든 문자열을 트리밍할 수 있는 우아한 방법을 찾고 있습니다.저는 사람들이 언제 어떻게 줄을 다듬는지 알고 싶습니다.
사용자 정의 모델 바인더를 만들고 문자열 값을 트리밍하는 방법에 대해 생각했습니다.그렇게 하면, 나의 모든 트리밍 논리가 한 곳에 포함됩니다.이것이 좋은 접근법입니까?이를 수행하는 코드 샘플이 있습니까?
public class TrimModelBinder : DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext,
ModelBindingContext bindingContext,
System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
{
if (propertyDescriptor.PropertyType == typeof(string))
{
var stringValue = (string)value;
if (!string.IsNullOrWhiteSpace(stringValue))
{
value = stringValue.Trim();
}
else
{
value = null;
}
}
base.SetProperty(controllerContext, bindingContext,
propertyDescriptor, value);
}
}
이 코드는 어떻습니까?
ModelBinders.Binders.DefaultBinder = new TrimModelBinder();
global.asax Application_Start 이벤트를 설정합니다.
이는 @takepara 동일한 해상도이지만 기본 모델 바인더 대신 IM 모델 바인더로서 global.asax에 모델 바인더를 추가하는 것입니다.
ModelBinders.Binders.Add(typeof(string),new TrimModelBinder());
클래스:
public class TrimModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueResult== null || valueResult.AttemptedValue==null)
return null;
else if (valueResult.AttemptedValue == string.Empty)
return string.Empty;
return valueResult.AttemptedValue.Trim();
}
}
@hacked post 기준: http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx
@takepara 답변에 대한 한 가지 개선 사항.
일부는 프로젝트에 참여했습니다.
public class NoTrimAttribute : Attribute { }
트림 내 모델 바인더 클래스 변경
if (propertyDescriptor.PropertyType == typeof(string))
로.
if (propertyDescriptor.PropertyType == typeof(string) && !propertyDescriptor.Attributes.Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute)))
[NoTrim] 특성을 사용하여 트리밍에서 제외할 특성을 표시할 수 있습니다.
ASP.Net Core 2에서는 이것이 저에게 효과가 있었습니다.사용 중입니다.[FromBody]
내 컨트롤러 및 JSON 입력에 속성이 있습니다.JSON 역직렬화에서 문자열 처리를 재정의하기 위해 나는 내 자신의 JSonConverter를 등록했습니다.
services.AddMvcCore()
.AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Insert(0, new TrimmingStringConverter());
})
변환기는 다음과 같습니다.
public class TrimmingStringConverter : JsonConverter
{
public override bool CanRead => true;
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => objectType == typeof(string);
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
if (reader.Value is string value)
{
return value.Trim();
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
C# 6의 향상된 기능으로 모든 문자열 입력을 트리밍하는 매우 작은 모델 바인더를 작성할 수 있습니다.
public class TrimStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var attemptedValue = value?.AttemptedValue;
return string.IsNullOrWhiteSpace(attemptedValue) ? attemptedValue : attemptedValue.Trim();
}
}
▁in▁somewhere▁this에 포함시켜야 .Application_Start()
의 신의에Global.asax.cs
때 할 파일입니다.string
s:
ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
바인더를 하는 것이 더 . 모델 바인더를 입니다.string
메서드 인수로 직접 지정하든 모델 클래스의 속성으로 지정하든 상관 없습니다.그러나 여기에 있는 다른 답변에서 제안하는 대로 기본 모델 바인더를 재정의하는 경우, 이는 모델에 대한 속성을 바인딩할 때만 작동하며, 모델에 대한 속성을 바인딩할 때는 작동하지 않습니다.string
로서.
편집: 필드의 유효성을 검사하지 않아야 하는 상황에 대한 설명자의 질문입니다.제 원래 답변은 OP가 제기한 질문을 다루기 위해 줄였지만, 관심이 있는 사람들은 다음과 같은 확장 모델 바인더를 사용하여 검증을 처리할 수 있습니다.
public class TrimStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var shouldPerformRequestValidation = controllerContext.Controller.ValidateRequest && bindingContext.ModelMetadata.RequestValidationEnabled;
var unvalidatedValueProvider = bindingContext.ValueProvider as IUnvalidatedValueProvider;
var value = unvalidatedValueProvider == null ?
bindingContext.ValueProvider.GetValue(bindingContext.ModelName) :
unvalidatedValueProvider.GetValue(bindingContext.ModelName, !shouldPerformRequestValidation);
var attemptedValue = value?.AttemptedValue;
return string.IsNullOrWhiteSpace(attemptedValue) ? attemptedValue : attemptedValue.Trim();
}
}
@takepara의 또 다른 변형이지만 반전이 있습니다.
저는 @Anton의 "NoTrim" 예보다 옵트인 "StringTrim" 속성 메커니즘을 선호합니다.
모델 상태가 올바르게 채워지고 기본 유효성 검사/승인/거부 패턴을 정상적으로 사용할 수 있도록 하려면 SetModelValue에 대한 추가 호출이 필요합니다.적용할 업데이트 모델(모델)과 모델 상태를 시도합니다.모든 변경사항을 적용하려면()를 선택 취소합니다.
엔티티/공유 라이브러리에 저장:
/// <summary>
/// Denotes a data field that should be trimmed during binding, removing any spaces.
/// </summary>
/// <remarks>
/// <para>
/// Support for trimming is implmented in the model binder, as currently
/// Data Annotations provides no mechanism to coerce the value.
/// </para>
/// <para>
/// This attribute does not imply that empty strings should be converted to null.
/// When that is required you must additionally use the <see cref="System.ComponentModel.DataAnnotations.DisplayFormatAttribute.ConvertEmptyStringToNull"/>
/// option to control what happens to empty strings.
/// </para>
/// </remarks>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class StringTrimAttribute : Attribute
{
}
그런 다음 MVC 애플리케이션/라이브러리에서 다음 작업을 수행합니다.
/// <summary>
/// MVC model binder which trims string values decorated with the <see cref="StringTrimAttribute"/>.
/// </summary>
public class StringTrimModelBinder : IModelBinder
{
/// <summary>
/// Binds the model, applying trimming when required.
/// </summary>
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Get binding value (return null when not present)
var propertyName = bindingContext.ModelName;
var originalValueResult = bindingContext.ValueProvider.GetValue(propertyName);
if (originalValueResult == null)
return null;
var boundValue = originalValueResult.AttemptedValue;
// Trim when required
if (!String.IsNullOrEmpty(boundValue))
{
// Check for trim attribute
if (bindingContext.ModelMetadata.ContainerType != null)
{
var property = bindingContext.ModelMetadata.ContainerType.GetProperties()
.FirstOrDefault(propertyInfo => propertyInfo.Name == bindingContext.ModelMetadata.PropertyName);
if (property != null && property.GetCustomAttributes(true)
.OfType<StringTrimAttribute>().Any())
{
// Trim when attribute set
boundValue = boundValue.Trim();
}
}
}
// Register updated "attempted" value with the model state
bindingContext.ModelState.SetModelValue(propertyName, new ValueProviderResult(
originalValueResult.RawValue, boundValue, originalValueResult.Culture));
// Return bound value
return boundValue;
}
}
바인더에서 속성 값을 설정하지 않으면 아무것도 변경하지 않으려는 경우에도 해당 속성을 ModelState에서 완전히 차단합니다!이것은 사용자가 모든 문자열 유형을 바인딩하는 것으로 등록되어 있기 때문에 (테스트에서) 기본 바인더가 이를 수행하지 않는 것으로 나타납니다.
ASP에서 이 작업을 수행하는 방법을 검색하는 사용자를 위한 추가 정보입니다.NET Core 1.0.논리는 꽤 많이 변했습니다.
어떻게 하는지 블로그에 글을 올렸는데, 좀 더 자세히 설명해줍니다.
ASP.NET Core 1.0 솔루션은 다음과 같습니다.
실제 트리밍을 수행하기 위한 모델 바인더
public class TrimmingModelBinder : ComplexTypeModelBinder
{
public TrimmingModelBinder(IDictionary propertyBinders) : base(propertyBinders)
{
}
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
if(result.Model is string)
{
string resultStr = (result.Model as string).Trim();
result = ModelBindingResult.Success(resultStr);
}
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
}
또한 최신 버전의 모델 바인더 공급자가 필요합니다. 이것은 이 바인더가 이 모델에 사용되어야 한다는 것을 알려줍니다.
public class TrimmingModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
{
var propertyBinders = new Dictionary();
foreach (var property in context.Metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
return new TrimmingModelBinder(propertyBinders);
}
return null;
}
}
그러면 Startup.cs 에 등록해야 합니다.
services.AddMvc().AddMvcOptions(options => {
options.ModelBinderProviders.Insert(0, new TrimmingModelBinderProvider());
});
MVC 코어의 경우
바인더:
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Threading.Tasks;
public class TrimmingModelBinder
: IModelBinder
{
private readonly IModelBinder FallbackBinder;
public TrimmingModelBinder(IModelBinder fallbackBinder)
{
FallbackBinder = fallbackBinder ?? throw new ArgumentNullException(nameof(fallbackBinder));
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != null &&
valueProviderResult.FirstValue is string str &&
!string.IsNullOrEmpty(str))
{
bindingContext.Result = ModelBindingResult.Success(str.Trim());
return Task.CompletedTask;
}
return FallbackBinder.BindModelAsync(bindingContext);
}
}
공급자:
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;
public class TrimmingModelBinderProvider
: IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!context.Metadata.IsComplexType && context.Metadata.ModelType == typeof(string))
{
return new TrimmingModelBinder(new SimpleTypeModelBinder(context.Metadata.ModelType));
}
return null;
}
}
등록 기능:
public static void AddStringTrimmingProvider(this MvcOptions option)
{
var binderToFind = option.ModelBinderProviders
.FirstOrDefault(x => x.GetType() == typeof(SimpleTypeModelBinderProvider));
if (binderToFind == null)
{
return;
}
var index = option.ModelBinderProviders.IndexOf(binderToFind);
option.ModelBinderProviders.Insert(index, new TrimmingModelBinderProvider());
}
레지스터:
service.AddMvc(option => option.AddStringTrimmingProvider())
쿼리 문자열 매개 변수 값과 양식 값을 트리밍하는 값 공급자를 만들었습니다.이것은 ASP를 통해 테스트되었습니다.NET Core 3은 완벽하게 작동합니다.
public class TrimmedFormValueProvider
: FormValueProvider
{
public TrimmedFormValueProvider(IFormCollection values)
: base(BindingSource.Form, values, CultureInfo.InvariantCulture)
{ }
public override ValueProviderResult GetValue(string key)
{
ValueProviderResult baseResult = base.GetValue(key);
string[] trimmedValues = baseResult.Values.Select(v => v?.Trim()).ToArray();
return new ValueProviderResult(new StringValues(trimmedValues));
}
}
public class TrimmedQueryStringValueProvider
: QueryStringValueProvider
{
public TrimmedQueryStringValueProvider(IQueryCollection values)
: base(BindingSource.Query, values, CultureInfo.InvariantCulture)
{ }
public override ValueProviderResult GetValue(string key)
{
ValueProviderResult baseResult = base.GetValue(key);
string[] trimmedValues = baseResult.Values.Select(v => v?.Trim()).ToArray();
return new ValueProviderResult(new StringValues(trimmedValues));
}
}
public class TrimmedFormValueProviderFactory
: IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
if (context.ActionContext.HttpContext.Request.HasFormContentType)
context.ValueProviders.Add(new TrimmedFormValueProvider(context.ActionContext.HttpContext.Request.Form));
return Task.CompletedTask;
}
}
public class TrimmedQueryStringValueProviderFactory
: IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
context.ValueProviders.Add(new TrimmedQueryStringValueProvider(context.ActionContext.HttpContext.Request.Query));
return Task.CompletedTask;
}
}
은 그다값공을에 합니다.ConfigureServices()
Startup.csStartup.cs합니다.
services.AddControllersWithViews(options =>
{
int formValueProviderFactoryIndex = options.ValueProviderFactories.IndexOf(options.ValueProviderFactories.OfType<FormValueProviderFactory>().Single());
options.ValueProviderFactories[formValueProviderFactoryIndex] = new TrimmedFormValueProviderFactory();
int queryStringValueProviderFactoryIndex = options.ValueProviderFactories.IndexOf(options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>().Single());
options.ValueProviderFactories[queryStringValueProviderFactoryIndex] = new TrimmedQueryStringValueProviderFactory();
});
위의 우수한 답변과 댓글을 훑어보고 점점 혼란스러워지다가 문득 '야, jQuery 해결책이 있을까'라는 생각이 들었습니다.그래서 저처럼 모델 바인더를 조금 당황스러워하는 사람들을 위해, 저는 양식이 제출되기 전에 입력 필드를 다듬는 다음과 같은 jQuery 스니펫을 제공합니다.
$('form').submit(function () {
$(this).find('input:text').each(function () {
$(this).val($.trim($(this).val()));
})
});
은 MVC 5.2.할 사항을 입니다.skipValidation
기본 제공 가치 제공자의 요구 사항.
public class TrimStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// First check if request validation is required
var shouldPerformRequestValidation = controllerContext.Controller.ValidateRequest &&
bindingContext.ModelMetadata.RequestValidationEnabled;
// determine if the value provider is IUnvalidatedValueProvider, if it is, pass in the
// flag to perform request validation (e.g. [AllowHtml] is set on the property)
var unvalidatedProvider = bindingContext.ValueProvider as IUnvalidatedValueProvider;
var valueProviderResult = unvalidatedProvider?.GetValue(bindingContext.ModelName, !shouldPerformRequestValidation) ??
bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
return valueProviderResult?.AttemptedValue?.Trim();
}
}
전역.asax
protected void Application_Start()
{
...
ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
...
}
업데이트: 이 답변은 최신 버전의 ASP에 대해 최신 버전이 아닙니다.NET 코어.대신 바셈의 대답을 사용하십시오.
ASP용.NET Core, 교체ComplexTypeModelBinderProvider
문자열을 다듬는 공급자와 함께.
시작 코드에서ConfigureServices
메소드, 추가:
services.AddMvc()
.AddMvcOptions(s => {
s.ModelBinderProviders[s.ModelBinderProviders.TakeWhile(p => !(p is ComplexTypeModelBinderProvider)).Count()] = new TrimmingModelBinderProvider();
})
정의TrimmingModelBinderProvider
다음과 같이:
/// <summary>
/// Used in place of <see cref="ComplexTypeModelBinderProvider"/> to trim beginning and ending whitespace from user input.
/// </summary>
class TrimmingModelBinderProvider : IModelBinderProvider
{
class TrimmingModelBinder : ComplexTypeModelBinder
{
public TrimmingModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders) : base(propertyBinders) { }
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
var value = result.Model as string;
if (value != null)
result = ModelBindingResult.Success(value.Trim());
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) {
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++) {
var property = context.Metadata.Properties[i];
propertyBinders.Add(property, context.CreateBinder(property));
}
return new TrimmingModelBinder(propertyBinders);
}
return null;
}
}
이것의 추악한 부분은 복사해서 붙여넣기입니다.GetBinder
로부터의 논리.ComplexTypeModelBinderProvider
하지만 당신이 이걸 피할 수 있는 고리는 없는 것 같아요
저는 그 해결책에 반대합니다.SetProperty에 대한 데이터가 ModelState에서도 채워질 수 있으므로 GetPropertyValue를 재정의해야 합니다.입력 요소에서 원시 데이터를 가져오려면 다음과 같이 기록합니다.
public class CustomModelBinder : System.Web.Mvc.DefaultModelBinder
{
protected override object GetPropertyValue(System.Web.Mvc.ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, System.Web.Mvc.IModelBinder propertyBinder)
{
object value = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
string retval = value as string;
return string.IsNullOrWhiteSpace(retval)
? value
: retval.Trim();
}
}
속성별 필터링설명자 속성문자열 값에만 관심이 있지만 입력되는 모든 항목이 기본적으로 문자열이기 때문에 중요하지 않을 경우 입력합니다.
속성 접근법을 제안하는 글이 많이 올라왔습니다.여기 이미 트림 특성과 많은 다른 특성을 가진 패키지가 있습니다: Dado.구성요소 모델.돌연변이 또는 누겟
public partial class ApplicationUser
{
[Trim, ToLower]
public virtual string UserName { get; set; }
}
// Then to preform mutation
var user = new ApplicationUser() {
UserName = " M@X_speed.01! "
}
new MutationContext<ApplicationUser>(user).Mutate();
Mutate(), 사용자에 대한 호출 후.사용자 이름이 다음으로 변환됩니다.m@x_speed.01!
.
이 예제에서는 공백을 잘라내고 문자열을 소문자로 대소문자를 구분합니다.그것은 검증을 도입하지 않지만,System.ComponentModel.Annotations
함께 사용할 수 있습니다.Dado.ComponentModel.Mutations
.
저는 이것을 다른 스레드에 올렸습니다.asp.net core 2에서 저는 다른 방향으로 갔습니다.대신 액션 필터를 사용했습니다.이 경우 개발자가 문자열 트리밍을 전체적으로 설정하거나 문자열 트리밍을 적용할 작업의 속성으로 사용할 수 있습니다.이 코드는 모델 바인딩이 수행된 후 실행되며 모델 개체의 값을 업데이트할 수 있습니다.
다음은 내 코드입니다. 먼저 작업 필터를 만듭니다.
public class TrimInputStringsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
foreach (var arg in context.ActionArguments)
{
if (arg.Value is string)
{
string val = arg.Value as string;
if (!string.IsNullOrEmpty(val))
{
context.ActionArguments[arg.Key] = val.Trim();
}
continue;
}
Type argType = arg.Value.GetType();
if (!argType.IsClass)
{
continue;
}
TrimAllStringsInObject(arg.Value, argType);
}
}
private void TrimAllStringsInObject(object arg, Type argType)
{
var stringProperties = argType.GetProperties()
.Where(p => p.PropertyType == typeof(string));
foreach (var stringProperty in stringProperties)
{
string currentValue = stringProperty.GetValue(arg, null) as string;
if (!string.IsNullOrEmpty(currentValue))
{
stringProperty.SetValue(arg, currentValue.Trim(), null);
}
}
}
}
이를 사용하려면 전역 필터로 등록하거나 TrimInputStrings 특성으로 작업을 장식합니다.
[TrimInputStrings]
public IActionResult Register(RegisterViewModel registerModel)
{
// Some business logic...
return Ok();
}
좋아요, 저는 이것을 가지고 있고 그것은 효과가 있습니다.
class TrimmingModelBinder : IModelBinder
{
public Task BindModelAsync (ModelBindingContext ctx)
{
if
(
ctx .ModelName is string name
&& ctx .ValueProvider .GetValue (name) .FirstValue is string v)
ctx .ModelState .SetModelValue
(
name,
new ValueProviderResult
((ctx .Result = ModelBindingResult .Success (v .Trim ())) .Model as string));
return Task .CompletedTask; }}
class AutoTrimAttribute : ModelBinderAttribute
{
public AutoTrimAttribute ()
{ this .BinderType = typeof (TrimmingModelBinder); }}
하지만 이것에 대한 표준 기능이 없다는 것은 유감입니다.
저는 @Kai G의 답변을 다음과 같이 수정했습니다.System.Text.Json
:
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class TrimmedStringConverter : JsonConverter<string>
{
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(string);
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.GetString() is string value ? value.Trim() : null;
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
}
언급URL : https://stackoverflow.com/questions/1718501/best-way-to-trim-strings-after-data-entry-should-i-create-a-custom-model-binder
'programing' 카테고리의 다른 글
에레독 배관을 위한 다중 라인 구문; 이것은 휴대 가능합니까? (0) | 2023.05.02 |
---|---|
구분 문자열을 목록으로 분할()하는 방법 (0) | 2023.05.02 |
MongoDB에서 참조된 개체를 쿼리하려면 어떻게 해야 합니까? (0) | 2023.05.02 |
PostgreSQL에서 상속된 테이블을 사용해야 하는 경우 (0) | 2023.05.02 |
Postgres SELECT에서 열을 연결하는 방법은 무엇입니까? (0) | 2023.05.02 |